CSS
Responsive layouts
- when building responsive layouts, default to the smallest (usually mobile) layout, and use media queries to adjust the layout for larger screens
rem vs em vs px
- use
rem
for values that should scale with the user's default font size (different from page zoom)- use
em
instead if the values are highly dependent on the current element's font size - in most browsers, the default font size is 16px if not modified by the user
- use
- examples:
- horizontal padding should usually use
px
, since you don't want to shrink the line width just because the text is larger - vertical margins between paragraphs should usually use
em
, since they should adapt to the paragraph's font size - borders and other decorative elements should use
px
- media queries should usually use
rem
- larger font sizes will increase the width of sidebars and other asides, which may make the main content area too narrow, so the mobile layout is often preferred- when used in media queries,
rem
always refers to the default font size, even if you set a custom font size on the root element in CSS
- when used in media queries,
- horizontal padding should usually use
Responsive font sizing without media queries
font-size: clamp(2rem, 8vw, 4rem)
- scales the font size based on the viewport width, with minimum and maximum based on the root font size (values are examples and should be adjusted)
#todo viewport units
Selectors
Attribute selectors
Operator | Meaning |
---|---|
~= | a whitespace-separated list of words, one of which is exactly value |
|= | can be exactly value or can begin with value immediately followed by a hyphen |
^= | starts with value |
$= | ends with value |
*= | contains value |
[... i] | case insensitive |
:focus-visible
- Like
:focus
, but only applies if the browser decides that focus should be shown visually (typically when using keyboard navigation)- Text fields apply
:focus-visible
even when clicked into (in Chrome at least)
- Text fields apply
- Use
:focus
as a fallback for browsers that don't support:focus-visible
button:focus-visible {
outline: 2px solid white;
}
@supports not selector(:focus-visible) {
button:focus {
outline: 2px solid white;
}
}
:user-valid and :user-invalid
- like
:valid
and:invalid
, but only apply once the user has interacted with the form element- useful for marking invalid fields without marking the entire page as invalid on load
:has
- matches elements where any of the given selectors match relative to the current element
- Invalid selectors are ignored
- lets you target elements based on their children or subsequent elements
- takes the specificity of its most specific argument
:has
cannot be nested inside another :has
!
div:has(+ span) {
/* matches divs immediately followed by a span */
}
div:has(> span) {
/* matches divs containing a span */
}
div:has(+ span, > span) {
/* matches either of the above cases */
}
:is
- matches any element that can be matched by any of the given selectors
- Invalid selectors are ignored
- takes the specificity of its most specific argument
:is(ol, ul) :is(ol, ul, :banana /* invalid selector is ignored */) {
/* matches any nested list, ordered or unordered */
}
:where
- same as [[#:is]] but with specificity 0
:nth-child
Limiting with selectors
- lets you limit
:nth-child
(and:nth-last-child
) to only "look at" elements that match the given selector
<article>
<h2>Example</h2>
<img class="hero" src="https://picsum.photos/seed/apple/200/100" />
<img class="inline" src="https://picsum.photos/seed/orange/200/100" />
<p>
Culpa irure anim occaecat ut voluptate duis aliquip consequat esse id id ad
aute labore. Dolore ipsum ipsum cillum veniam. Nisi ex eiusmod sunt et
veniam.
</p>
<img class="inline" src="https://picsum.photos/seed/banana/200/100" />
<p>
Minim magna ullamco nostrud laboris quis reprehenderit minim ex et. Ipsum
velit Lorem sint ex in aliqua tempor non sunt enim consequat incididunt
adipisicing do.
</p>
</article>
- float inline images in alternating directions
article > img.inline {
float: left;
}
/* this won't work, because both img.inline have odd indexes! */
article > img.inline:nth-child(even) {
float: right;
}
/* but this will */
article > img:nth-child(even of .inline) {
float: right;
}
- the
nth-child
selector can't "see" selectors outside itself - in the example below, we want to select the 2nd
.important
item after.anchor
(should be item 8)
<ul>
<li class="important">Item 1</li>
<li>Item 2</li>
<li class="anchor">Item 3</li>
<li>Item 4</li>
<li class="important">Item 5</li>
<li>Item 6</li>
<li>Item 7</li>
<li class="important">Item 8</li>
<li class="important">Item 9</li>
<li>Item 10</li>
</ul>
/* ❌ this will select item 5 - it's after .anchor, and *separately* it's the second child with .important */
li.anchor ~ :nth-child(2 of .important) {
color: lime;
}
/* ✅ This will select item 8, because it's the second child that is *after .anchor and has .important* */
:nth-child(2 of li.anchor ~ .important) {
color: deepskyblue;
}
Properties
align- and justify-
- grid:
align
moves along the block axisjustify
moves along the inline axis-content
moves the grid areas within their container (if they don't take up the whole container)-items
moves grid items inside their grid area
- flexbox:
justify-content
moves children along the main axis (corresponding to theflex-direction
)- by default this matches grid, as the default
flex-direction
isrow
- by default this matches grid, as the default
align-items
moves children along the cross axisalign-content
moves wrapping flex lines within their container, and has no effect on non-wrapping flex containers
- block:
- as of April 2024,
align-content
can be used in block layout to align children along the block axis
- as of April 2024,
Shorthand (place-)
- shorthand for
align-{property} justify-{property}
Center elements within a container
.container {
display: grid;
place-content: center;
}
animation
Shorthand
Property | Default |
---|---|
duration | 0s |
timing-function | ease |
delay | 0s |
iteration-count | 1 |
direction | normal |
fill-mode | none |
play-state | running |
name | none |
animation: 3s ease-in 1s infinite reverse both running slidein;
aspect-ratio
- Keeps the element at the specified aspect ratio (
width / height
)
aspect-ratio: 16 / 9;
backdrop-filter
Nested backdrop filters
backdrop-filter not working for nested elements in Chrome
I have a div.outer and inside a div.inner, both with position: absolute; and backdrop-filter: blur(8px);. https://jsbin.com/nihakiqocu/1/edit?html,css,output Safari (left) gives the desired resul...
https://stackoverflow.com/a/76207141
Setting any of these values on an element turns it into a backdrop root:
filter
backdrop-filter
mix-blend-mode
opacity
< 1mask
or any mask sub-propertieswill-change
specifying any of the above values
Child elements can't "see through" a backdrop root, so backdrop-filter
on children won't work as expected.
To fix this, move the backdrop-filter
that's on the outer element to a pseudo-element:
.blurred-bg::before {
content: '';
position: absolute;
width: 100%;
height: 100%;
backdrop-filter: blur(30px);
z-index: -1;
}
background
- if you have multiple backgrounds, earlier ones are drawn on top
Shorthand
- separate position and size (in that order) with a slash:
center/80%
or50% 50% / 0% 0%
- If only one
box
value is specified, it sets bothorigin
andclip
color
can only be specified on the last layer
Property | Default |
---|---|
image | none |
position | 0% 0% |
size | auto auto |
origin | padding-box |
clip | border-box |
attachment | scroll |
repeat | repeat |
color | transparent |
background: repeat scroll 0% 0%/auto padding-box border-box none transparent;
box-decoration-break
In Safari, requires -webkit-
prefix and only works on inline elements
- copy decorations like borders and backgrounds to each line of wrapping text
box-decoration-break: clone;
without box-decoration-break
with box-decoration-break
box-shadow
Shorthand
inset? | offset-x | offset-y | blur-radius | spread-radius | color
box-shadow: 3px 3px red, inset -1em 0 0.4em blue;
Limit shadows to one side using clip-path
box-shadow: 0 0 16px 0 black;
clip-path: inset(0 0 -16px 0);
break-before, break-inside, break-after
There are many values, but these are the most common ones that are widely supported as of June 2024
- decide whether page or column breaks are allowed before, after, or inside an element
avoid
: don't allow page or column breaks- before and after only:
page
: force a page break before/after
- inside only:
avoid-column
andavoid-page
: don't allow breaks of that type inside
clip-path
inset
- Simple insets from each side, clockwise from top
clip-path: inset(5% 10% 15% 20%);
polygon
- Can have any number of points, values are
<x> <y>
/* equilateral triangle */
clip-path: polygon(50% 0, 0% 100%, 100% 100%);
/* trapezoid that narrows at the top */
clip-path: polygon(20% 0, 80% 0, 100% 100%, 0% 100%);
circle
radius at center
clip-path: circle(50% at 50% 50%);
ellipse
radiusX radiusY at center
clip-path: ellipse(25% 40% at 50% 50%);
color-scheme
- choose whether the element adapts to the system color scheme
normal
(default): use the browser's default color schemelight
ordark
will force the element to the specified color schemelight dark
means the element should support light and dark modes, and prefer light modedark light
is the same, but will prefer dark mode
only light
oronly dark
prevents the browser from overriding the color scheme
- use prefers-color-scheme as usual to style elements based on the color scheme, or use [[#light-dark]] as a shortcut for setting colors
- consider using [[#System color keywords]] to make the page match the system
- you can set
color-scheme
on the page root if your page has its own theme picker - add a
color-scheme
meta tag to render the correct page background before the CSS loads
<meta name="color-scheme" content="dark-light">
contain
- isolate an element and its contents from the rest of the document
- useful for reducing layout recalculation
- values (multiple can be used, separated with spaces):
size
: the element's size is unaffected by its contentsinline-size
: same assize
but for the inline direction only (can't be combined withsize
)layout
: nothing outside the element affects the element's internal layout, and vice versa- for example,
position: fixed
children won't escape the element, and margins won't collapse with the parent's margin
- for example,
style
: counters and quotes are scoped to the element and its contentspaint
: the element's contents will not be drawn outside its border-box- this means if the element is off screen, its children don't need to be rendered because they can't escape the element's box
strict
: same assize layout paint style
content
: same aslayout paint style
- use
contain: content
(or better, [[#content-visibility]]: auto) for elements such as articles that are independent from the rest of the page
contain-intrinsic-size
- lets you set a placeholder size for elements affected by size containment (such as from [[#contain]] or [[#content-visibility]])
- values are width and height
- can also use
contain-intrinsic-height
,contain-intrinsic-width
,contain-intrinsic-block-size
,contain-intrinsic-inline-size
- can also use
- the
auto
keyword tells the browser to remember the last rendered size, and use that as the intrinsic size if available
/* will behave as 300x100 while in size containment */
contain-intrinsic-size: 300px 100px;
/* will behave as 300x100 in size containment until rendered, then remember its actual size */
contain-intrinsic-size: auto 300px 100px;
container, container-name, container-type
See [[#Marking containers]]
content-visibility
- controls whether the browser renders the element's content - doesn't affect the box of the element itself!
- this includes text nodes and pseudo-content
visible
: the default (normal rendering)hidden
: the element's contents aren't rendered (similar todisplay: none
), and are hidden from the accessibility tree and Find featureauto
: the contents are only rendered if the element is "relevant to the user" - in or near the viewport, focused, selected, or in the top layerauto
elements get layout, style, and paint containment, and size containment if off-screen- the contents of
auto
elements that aren't being rendered still appear in the accessibility tree and Find feature- styles aren't rendered for the contents, so they'll still appear in the accessibility tree even if they have
display: none
orvisibility: hidden
- usearia-hidden="true"
to hide them from accessibility
- styles aren't rendered for the contents, so they'll still appear in the accessibility tree even if they have
- use the
contentvisibilityautostatechange
to start and stop expensive JavaScript based on the visibility state
- example usage: on a blog page, wrap each article in
content-visibility: auto
so only visible articles are rendered- use [[#contain-intrinsic-size]] to keep the scrollbar accurate
Improving rendering performance with CSS content-visibility
font-weight
Value | Common weight name |
---|---|
100 | Thin (Hairline) |
200 | Extra Light (Ultra Light) |
300 | Light |
400 | Normal (Regular) |
500 | Medium |
600 | Semi Bold (Demi Bold) |
700 | Bold |
800 | Extra Bold (Ultra Bold) |
900 | Black (Heavy) |
950 | Extra Black (Ultra Black) |
forced-color-adjust
- Use
none
to override an element's colors when in [[#forced-colors]] mode- respect the user's choices - only use this if the colors the browser applies aren't readable
.button {
forced-color-adjust: none;
}
gap
- Sets spacing between tracks in flex or grid views
- Unlike margins,
gap
doesn't apply to the outer edges
- Unlike margins,
- First value is gap between rows, second is gap between columns
- If only one value is given, it applies to both
- There are also separate
row-gap
/column-gap
properties
gap: 10px 5px;
hyphens
none
: words are never brokenmanual
(default): words are only broken at line break opportunities (-
characters or­
)auto
: the browser uses its dictionary to break words automatically
inset
- Shorthand for
top right bottom left
(same syntax asmargin
orpadding
)
inset: 1rem 2rem; /* top: 1rem; right: 2rem; bottom: 1rem; left: 2rem; */
inset-block, inset-inline
- Short for
inset-block-start inset-block-end
(or inline)
inset-block: 50px 100px; /* 50px start, 100px end */
inset-inline: 20px; /* 20px start and end */
interpolate-size and calc-size()
As of November 2024, supported in Chromium only
interpolate-size: allow-keywords
lets you transition or animate between a length or percentage, andauto
or another intrinsic size- one of the values must be a length or percentage
- set this on the root to enable it for the entire page
calc-size(size, expression)
behaves likecalc()
, but allows you to include an intrinsic size in the calculation (given as the first argument, and referred to in the expression with thesize
keyword)- only one intrinsic size can be used per calculation
width: calc-size(min-content, size + 100px)
mask
- Requires
-webkit-
prefix on Chromium browsers - Useful for changing the color of SVG icons - set the
background-color
as the color you want and use the SVG as themask-image
mask-image: url('image.svg');
mask-mode: alpha; /* or luminance */
mask-composite: add; /* or subtract, intersect, exclude - if you have multiple masks, this controls how each one is composited with the masks below it */
/* these accept the same values as their background- equivalents */
mask-size: auto;
mask-position: center;
mask-clip: border-box;
mask-repeat: repeat;
Fade using a gradient
- use a linear-gradient from white to transparent for
mask-image
.fade p {
mask-image: linear-gradient(to bottom, white, transparent);
}
Reprehenderit est tempor minim id cupidatat mollit velit sit. Eu magna ex nisi aute. Quis id culpa in ex incididunt est aliquip anim consectetur ipsum Lorem. Aute ullamco Lorem laboris tempor fugiat duis ex reprehenderit tempor et. Occaecat velit laborum sint aliquip eiusmod cillum sint esse officia. Nostrud minim duis anim minim tempor consectetur sit proident laboris ea et eiusmod. Irure aliquip ex amet cillum anim anim irure est ex cillum qui culpa aute.
mix-blend-mode
- controls how an element blends with its background
- use
mix-blend-mode: difference
to reverse text color of a progress bar
overscroll-behavior
contain
: prevents scroll chaining (when scrolling past the edge of the container starts to scroll outside the container)none
: prevents scroll chaining, and also prevents the "bounce" effect and pull to refresh
outline-offset
- Adjusts the amount of space between an element's edge and its outline
- can be positive or negative
print-color-adjust
- control whether the browser is allowed to optimize elements for printing (ex. by removing backgrounds or changing colors)
economy
(default): the browser can adjust the element's appearance for printexact
: don't change the element's appearance- make sure to test that the element looks right when printed!
resize
- lets you add a resize handle to elements
- values:
horizontal
,vertical
,both
,none
(default) - doesn't work on inline elements, or if
overflow
isvisible
text-shadow
Shorthand
offset-x | offset-y | blur-radius | color
text-shadow: 1px 1px 2px black, 0 0 1em blue, 0 0 0.2em blue;
text-wrap
As of November 2024 pretty
is only supported in Chromium, stable
only in Firefox and Safari
- controls how text is wrapped
wrap
andnowrap
: same as [[#white-space]]balance
: wraps and tries to keep the line length equal- only works for blocks of text with 6 or less lines
pretty
: wraps and favors better layout over speedstable
: wraps, but when the user is editing content, lines before the line being edited will remain static and not re-wrap
wrap
Est dolor excepteur exercitation adipisicing. Aute occaecat cillum esse nulla do eiusmod. In et non mollit do incididunt nisi cupidatat duis. Excepteur eu excepteur dolore nisi occaecat eu enim deserunt.
pretty
Est dolor excepteur exercitation adipisicing. Aute occaecat cillum esse nulla do eiusmod. In et non mollit do incididunt nisi cupidatat duis. Excepteur eu excepteur dolore nisi occaecat eu enim deserunt.
balance
Est dolor excepteur exercitation adipisicing. Aute occaecat cillum esse nulla do eiusmod. In et non mollit do incididunt nisi cupidatat duis. Excepteur eu excepteur dolore nisi occaecat eu enim deserunt.
touch-action
pan-x pan-y
: allow single-finger panning, but not pinch zoom or double-tap zoompinch-zoom
: allow multi-finger panning and pinch zoom, but not single finger panningmanipulation
: disable double-tap zoom, but allow pan and pinch-zoom- same as
pan-x pan-y pinch-zoom
- same as
none
: disable all pan and zoom gestures
transition
Transition from height 0 to auto
- Place the element in a container with this styling
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows;
- To transition to auto height, change
grid-template-rows
to1fr
transition-behavior
- lets you transition properties that are discrete (things like
display: none
that can't be interpolated)- discrete properties will swap from one state to another at 50%, with no smooth transition
- exceptions for
display: none
orcontent-visibility: hidden
: the browser will make sure the content is displayed during the entire animation- in the below example, the card will remain visible until the fade out is finished
- exceptions for
- discrete properties will swap from one state to another at 50%, with no smooth transition
- can be used in the
transition
longhand, but you should include a version without it first for browsers that don't support it
.card {
transition-property: opacity, display;
transition-duration: 0.25s;
transition-behavior: allow-discrete;
}
.card.fade-out {
opacity: 0;
display: none;
}
user-select
- Lets you control how text selection in an element works, or disable it entirely
none
: disable text selection within the bounds of this elementall
: select all the text within this element on clickÏ
user-select: none
should be used sparingly, and only for UI text that a user isn't likely to want to copy (like button labels).
user-select: none;
white-space
- spaces include space characters, tabs, and segment breaks (such as newlines)
- hang means that the character may be placed outside the box and does not affect sizing
New lines | Spaces and tabs | Text wrapping | End-of-line spaces | End-of-line other space separators | |
---|---|---|---|---|---|
normal |
Collapse | Collapse | Wrap | Remove | Hang |
nowrap |
Collapse | Collapse | No wrap | Remove | Hang |
pre |
Preserve | Preserve | No wrap | Preserve | No wrap |
pre-wrap |
Preserve | Preserve | Wrap | Hang | Hang |
pre-line |
Preserve | Collapse | Wrap | Remove | Hang |
break-qspaces |
Preserve | Preserve | Wrap | Wrap | Wrap |
zoom
- enlarges an element and affects layout (unlike
transform
orscale
)- elements are enlarged from the start of the block direction and center of the inline direction (top center by default)
- can take numeric values or percentages: 1.1 = 110%
- in the example below, the square with
zoom
affects layout, so its container expands to fit it, while the square withscale
doesn't
Flexbox
Make flex children the same size
Use flex: 1 1 100%
to make every flex child the same size, regardless of content.
Prevent flex and grid items from overflowing container
By default, flex and grid items have min-width: auto
and min-height: auto
, meaning they can't be smaller than their content.
If the items are overflowing their container, set min-width: 0
or min-height: 0
, or any overflow
value other than visible on them.
Grid
- Use
order
to rearrange grid items - Use [[#gap]] to create gutters between grid tracks
Grid container properties
grid-template-columns, grid-template-rows
- Defines the count and size of explicit grid tracks (rows or columns)
grid-template-columns: 50% 50%;
/* these are the same */
grid-template-rows: 20% 20% 20% 20% 20%;
grid-template-rows: repeat(5, 20%);
- Use
auto
for automatic sizing, ornone
to remove the explicit grid
fr
- The
fr
unit represents one "part" of the available space - Unlike percentages,
fr
divides up extra space - columns won't overflow, even if that means breaking the proportions given
grid-template-columns: 1fr 3fr;
grid-template-columns: 25% 75%;
fr
also ignores [[#gap]], while percentages don't (since they're based on the total area of the grid container)
minmax
- Item size will be >= min and <= max
- use
minmax(0, 1fr)
to keep all rows/columns the same size
auto-fill
- Use the
auto-fill
keyword to create as many tracks as will fit in the container
grid-template-columns: repeat(auto-fill, 200px);
auto-fit
- Use
auto-fit
with [[#minmax]] to make the tracks expand to fit any leftover space
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
Named lines
- You can name grid lines to make them easier to reference with [[#grid-row-start, grid-column-start, grid-row-end, grid-column-end]]
- You can give the same line multiple names separated by spaces
- If you name lines with
-start
and-end
, the browser will generate an implicit named area between them (in the below example,sidebar
andarticle
)- The opposite is true: if you create a grid area named
article
, it will generate named lines in each direction calledarticle-start
andarticle-end
- The opposite is true: if you create a grid area named
main {
grid-template-columns:
[sidebar-start] 200px
[sidebar-end article-start] 1fr [article-end];
}
.sidebar {
grid-column: sidebar-start / sidebar-end;
}
.article {
grid-column: article-start / article-end;
}
article-start article-end
grid-template-areas
- Lets you name certain grid areas, to make it easier to assign elements to them using [[#grid-area]]
- Also lets you change the layout without having to change styles for all the child elements
- Each string is a row, and each space-separated token within the string is a column
- strings don't need to be on separate lines (ie. you could write
"a a a" "b c c" "b c c"
), but putting them on separate lines makes them more readable
- strings don't need to be on separate lines (ie. you could write
- Row heights go after each string, and column widths are at the end after a
/
- Areas must be rectangular (ex. you can't do
"a a b" "a b b"
) - [[#Named lines]] named with
-start
and-end
will generate an implicit named area- Conversely, named areas will generate implicit named lines with
-start
and-end
appended
- Conversely, named areas will generate implicit named lines with
- Named areas created this way can't overlap, but you can create [[#Named lines]] with
-start
and-end
suffixes to create overlapping named areas
.grid {
display: grid;
grid-template-areas:
"a a a"
"b c c"
"b c c";
}
- Use one or more dots to leave an empty space
grid-template-areas:
". a a"
"b c c"
"b c c";
- use media queries to make area layouts responsive
grid-template
- Shorthand for [[#grid-template-columns, grid-template-rows]]
grid-template: repeat(auto-fill, 200px) / repeat(2, 1fr);
- Can also be a shorthand for [[#grid-template-areas]] and row/column sizes
- Row heights go after each row, column widths go at the end separated by a
/
- Row heights go after each row, column widths go at the end separated by a
grid-template:
"a a a" 100px
"b c c" 50px
"b c c" 50px / repeat(3, 1fr);
grid-auto-flow
- Controls the direction (
row
orcolumn
) that implicit grid tracks are created in (default:row
) - Add
dense
to "fill in" holes earlier in the grid - this may cause items to display out of order
grid-auto-rows, grid-auto-columns
- By default, implicit rows/columns are sized to fit their content, this property lets you give them an explicit size
grid-auto-rows: 100px;
grid-auto-columns: minmax(100px, auto);
Grid item properties
grid-row-start, grid-column-start, grid-row-end, grid-column-end
- Set which grid lines (not tracks!) an element's starting or ending edges touch (1-indexed)
- An element with
start: 1
andend: 4
will span three cells
- Order doesn't matter when using integers:
start: 4, end: 1
is the same asstart: 1, end: 4
- Negative values start counting from the end:
start: -2
will align the starting edge of the element to the next-to-last grid line- You can mix positive and negative:
start: 1, end: -1
will span the whole track
- You can mix positive and negative:
- Use the
span
keyword to declare how many cells an element takes up
/* this element will be 2 cells wide */
grid-column-start: 2;
grid-column-end: span 2;
- Can also use [[#Named lines]] (don't put the name in quotes)
- If the specified lines are outside the bounds of the [[#grid-template]], the browser will generate implicit grid tracks for the item
- You can use this and [[#grid-auto-flow]] to accomplish some layouts without defining a
grid-template
at all
- You can use this and [[#grid-auto-flow]] to accomplish some layouts without defining a
grid-row, grid-column
- Shorthand for the above:
start / end
grid-area
- Shorthand for
grid-row / grid-column
, orgrid-row-start / grid-column-start / grid-row-end / grid-column-end
- Can also specify a named area from [[#grid-template-areas]] (not in quotes)
position: absolute
- Grid items with
position: absolute
will take their grid area as their containing block if they have one, or the entire grid if they don't- Make sure the grid container has
position: relative
- Make sure the grid container has
- In the example below, the highlighted block has
grid-column: 2 / 3
set
display: contents
- Use
display: contents
to group grid children together, while still laying them out as part of the grid
See also
Functions
min, max
- you can perform calculations inside these functions without needing
calc()
width: min(100vw - 3rem, 80ch)
clamp
clamp(min, ideal, max)
- the middle value will be used as long as it's between min and max
min
andmax
should use a different unit thanideal
for it to have an effect- can perform calculations without needing
calc()
- in this example, the font size will adjust with the viewport width, but will never be smaller than
1rem
or larger than3rem
font-size: clamp(1rem, 4vw, 3rem)
color-mix
color-mix(in oklab, red 25%, blue)
- There are lots of different color spaces,
oklab
tends to give the most "natural-looking" result
Relative color syntax
- lets you create a color from another color without needing [[#color-mix]], and edit individual color components
- supported in all browsers as of July 2024
/* the input color can be in any format */*
color: rgb(from deepskyblue r g b);
/* can mix and match the individual components and numbers */
color: rgb(from deepskyblue g g 0);
/* or do math on them */
/* r g b channels are out of 255 */
color: rgb(from deepskyblue r calc(g * 2) b);
/* h is out of 255, s and l are out of 1 */
color: hsl(from deepskyblue h calc(s + .5) l);
Adjust transparency/alpha
- alpha is separated by a slash
/* reduce opacity by 50% */
color: rgb(from deepskyblue r g b / calc(alpha * 0.5))
/* reduce to 25% opacity */
color: hsl(from deepskyblue h s l / 0.25);
Lighten or darken a color
- OKLAB provides the most reliable results (see [[#color-mix]])
/* lightens the color by 25% */
color: oklab(from deepskyblue calc(l + 25) a b);
/* adjusts to 75% lightness regardless of the original value */
color: oklab(from deepskyblue 75% a b);
Saturate a color
color: hsl(from deeppink h calc(s + 0.5) l);
- use chroma instead of saturation to enter high dynamic range
color: oklch(from deeppink l calc(c + 0.5) h);
Create a color palette from a single color
Varied lightness:
:root {
--base-color: deeppink;
--color-0: oklch(from var(--base-color) calc(l + 20) c h); /* lightest */
--color-1: oklch(from var(--base-color) calc(l + 10) c h);
--color-2: var(--base-color);
--color-3: oklch(from var(--base-color) calc(l - 10) c h);
--color-4: oklch(from var(--base-color) calc(l - 20) c h); /* darkest */
}
Hue rotation:
:root {
--base-color: blue;
--primary: var(--base-color);
--secondary: oklch(from var(--base-color) l c calc(h - 45));
--tertiary: oklch(from var(--base-color) l c calc(h + 45));
}
Combine the two:
:root {
--base-color: deeppink;
--color-1: var(--base-color);
--color-2: oklch(from var(--base-color) calc(l - 10) c calc(h - 10));
--color-3: oklch(from var(--base-color) calc(l - 20) c calc(h - 20));
--color-4: oklch(from var(--base-color) calc(l - 30) c calc(h - 30));
--color-5: oklch(from var(--base-color) calc(l - 40) c calc(h - 40));
}
light-dark
- requires [[#color-scheme]] to be set
- lets you define light and dark colors (in that order) on one line
- only works for colors
:root {
color-scheme: light dark;
}
p {
color: light-dark(black, white);
}
At-rules
@container (container queries)
- allow you to apply styles to an element based on the size or computed properties of a specific ancestor (the container)
- container queries cannot be used to apply styles to the container itself!
.button {
container-name: button;
@container button style(--type: warning) {
/* this doesn't work because we're trying to apply rules to the container that's being queried */
background-color: red;
& span {
/* but this is fine */
color: white;
}
}
@container style(--type: warning) {
/* this is valid if a parent of .button has --type: warning */
background-color: red;
}
}
Marking containers
- containers are marked with the
container-type
property- two values:
inline-size
allows you to query the inline size only,size
allows you to query both directions- use
size
sparingly for performance reasons
- use
- two values:
- you can name containers using
container-name
, and reference them by name after@container
- containers can have multiple (space-separated) names, and names can be shared between multiple elements/selectors
- shorthand:
container: name / type
- containers have layout, style, and size or inline-size (depending on container type) containment applied
- this means a container's size won't be affected by its children, so containers need to have a set size of their own
Size queries
- supported in all major browsers as of February 2023
- style elements based on the size of a container's content-box
- padding isn't included, even if the element has
box-sizing: border-box
- padding isn't included, even if the element has
- supported query conditions:
width
,height
,block-size
,inline-size
,aspect-ratio
,orientation
- the first 4 require containment in that direction,
aspect-ratio
andorientation
requirecontainer-type: size
- the first 4 require containment in that direction,
- container queries have their own length units that correspond to viewport units -
cqw
forvw
,cqmin
forvmin
, etc- If not in a container, these units equal their corresponding viewport unit
Create a grid that collapses when its container is below a certain size:
<section>
<div class="grid"></div>
</section>
section {
container-type: inline-size;
}
.grid {
grid-template-columns: repeat(auto-fit, minmax(0%, 1fr));
}
@container (max-width: 500px) {
.grid {
grid-template-columns: 1fr;
}
}
-
by default, rules in container queries apply to the nearest ancestor container of the element receiving the rules
- this means that if there are multiple sets of rules in a container query, each set could have a different container
-
can use
and
,or
, andnot
operators to combine conditionsnot
can only be used once, and can't be combined withand
/or
<article>
<h2>Baked Ziti</h2>
<section>
<h3>Ingredients</h3>
...
</section>
<section>
<h3>Directions</h3>
...
</section>
</article>
article {
/* can be targeted with the name `article` or `recipe` */
container-name: article recipe;
container-type: inline-size;
}
article section {
/* shorthand */
container: recipe-section / inline-size;
}
/* this will base header font size on the <article> width,
not the section width */
@container recipe (min-width: 500px) {
h3 {
font-size: 1.33em;
}
}
/* same as above */
@container recipe (width >= 500px) {
h3 {
font-size: 1.33em;
}
}
/* operator example */
@container recipe (width >= 500px) and (width < 1000px) {
h3 {
font-size: 1.33em;
}
}
Style queries
As of November 2024, style queries for custom properties are not supported in Firefox, and no browsers support style queries for non-custom properties
- lets you query based on the computed values of properties on a container
- currently only works with custom properties, will eventually work with any property
- all elements are style containers - if a container name is not given, the container is the parent of the element receiving the styles
- for pseudo-elements (::before and ::after), the default container is the element the pseudo-element is attached to
- when querying a non-inherited property, be sure to explicitly specify a container name, since the parent's value may not be relevant
@container style(--alignment) {
/* rules will apply if the value of --alignment differs from the initial value */
}
@container card style(--alignment: start) {
/* rules will apply if the value of --alignment on the `card` container is `start` */
}
@container card style(--alignment: start) or style(--alignment: end) {
/* operators work here too */
}
- not yet supported by any browsers: make elements that are usually italic normal if they're inside an italicized container
/* not supported by any browsers as of November 2024! */
@container style(font-style: italic) {
em, i {
font-style: normal;
}
}
@import (don't use it)
Avoid using @import
, as it makes the browser download CSS sequentially and slows down rendering. Instead, link the stylesheets separately in your HTML, or use a bundler to combine them into one stylesheet.
@layer (Cascade Layers)
- Layers that are declared later take higher priority
- You can declare layers at the same time as adding rules, but it's clearer to declare the layer names first and then add rules
- You can also specify a layer name when using
@import
(though you should avoid using @import)
@layer reset, base, theme, utilities;
@import url(reset.css) layer(reset);
@layer base {
button.filled {
background-color: black;
color: white;
}
}
@layer theme {
button {
background-color: blue;
}
button.unthemed {
background-color: revert-layer;
}
}
- This button will have a blue background, because even though the rule in the base layer is more specific, the theme layer wins because it has a higher priority
<button class="filled">Click me!</button>
Rules outside of layers take priority over layered rules!
- Use the
revert-layer
keyword to "roll back" a value to the previous layer- Behaves like
revert
if used outside a layer
- Behaves like
- This button will have a black background
<button class="filled unthemed">Click me!</button>
- When using
!important
, the layer order is reversed: a!important
rule in base will override a!important
rule in theme- this means that
!important
rules in layers override unlayered!important
rules
- this means that
- Layers can have sub-layers, which can be referred to using dot notation
@layer framework {
@layer base {
...
}
@layer theme {
...
}
}
@layer framework.theme {
...
}
@media (media queries)
print and screen
- no parentheses needed
@media screen { }
@media print { }
prefers-color-scheme
@media (prefers-color-scheme: dark) { }
@media (prefers-color-scheme: light) { }
hover and any-hover
hover
tests if the primary input method can hover,any-hover
tests if any input method can- ex. on an iPad with a mouse connected,
hover
is false butany-hover
is true
- ex. on an iPad with a mouse connected,
@media (hover: hover) {
/* the primary input device can hover */
}
@media (any-hover: none) {
/* none of the input devices can hover */
}
prefers-reduced-motion
@media (prefers-reduced-motion: reduce) {
/* disable transitions or replace with a simple fade */
}
@media (prefers-reduced-motion: no-preference) { }
prefers-reduced-transparency
Supported in Chromium only as of November 2024
@media (prefers-reduced-transparency: reduce) {
/* remove transparency and backdrop-filters */
}
@media (prefers-reduced-transparency: no-preference) { }
forced-colors
- detect high contrast mode and other situations where the browser is overriding the page colors
- should be used for small tweaks only - respect the user's choices and don't try to override them
- in forced colors mode, the browser forcibly applies appropriate system colors to elements, and removes shadows and non-URL background-images (like gradients)
- use [[#System color keywords]] to adjust colors if necessary
- also see [[#forced-color-adjust]]
@media (forced-colors: active) { }
@media (forced-colors: none) { }
scripting
@media (scripting: none) {
/* no JavaScript available */
}
@media (scripting: initial-only) {
/* JavaScript only available during initial page load */
}
@media (scripting: enabled) {
/* JavaScript fully available */
}
Boolean operators
and
@media (orientation: portrait) and (min-width: 300px) {
/* portrait *and* at least 300px wide */
}
Multiple queries (or)
- use a comma to match one of several queries, just like with selectors
@media (orientation: landscape), (min-width: 300px) {
/* landscape *or* at least 300px wide */
}
not
@media not (hover) { }
not
applies to an entire query, but only to one query in a comma-separated list
@media not screen and (color) { }
/* equivalent to */
@media not (screen and (color)) { }
@media not screen and (color), print and (color) { }
/* equivalent to */
@media (not (screen and (color))), print and (color) { }
Ranges
@media (width >= 300px) { }
@page
- lets you adjust the margins of printed pages
- use absolute units only!
- pseudo-classes:
:first
,:left
,:right
,:blank
(matches empty pages as a result of forced page breaks)
@page {
margin: 1rem 0;
}
@page :first {
margin-block-start: 0;
}
@property
- lets you define CSS custom properties with control over data type, inheritance, and initial value
- can be used to create animatable/transitionable custom properties
@property --property-name {
syntax: "<color>";
inherits: false;
initial-value: #c0ffee;
}
-
Aallowed
syntax
values:<length>
<number>
<percentage>
<length-percentage>
(same as<length> | <percentage>
)<color>
<image>
<url>
<integer>
<angle>
<time>
<resolution>
<transform-function>
<transform-list>
<custom-ident>
(see examples)
-
Examples:
syntax: "<color>";
syntax: "<length> | <percentage>";
syntax: "small | medium | large"; /* custom ident example */
syntax: "*"; /* any value */
@scope
Not supported in Firefox as of June 2024
- lets you limit a rule's reach based on a parent selector
@scope (.card) {
img {
/* only images inside .card elements get this border */
border: 2px solid black;
}
}
:scope
matches the scope root&
matches the selector used for the scope root - the difference is that&
can be used multiple times
@scope (.card) {
:scope {
/* selects the .card element itself */
}
& & {
/* selects a .card inside the matched .card */
}
}
@scope
can accept a second argument to limit the reach- inheritance still passes through the scope bounds
@scope (.card) to (.card-content) {
/* p elements inside .card-content won't be affected */
p {
font-weight: bold;
}
:scope {
/* but they'll still inherit this text color if it's not overridden */
color: gray;
}
}
@scope (.card) to (:scope > .content) {
/* elements inside .content won't be affected, but only if .content is a direct child of .card */
}
/* the limit can reference elements outside the scope */
@scope (.card) to (.sidebar :scope .content) {]
/* .content only limits the scope if the card is inside .sidebar */
}
@scope
doesn't affect specificity:scope
has specificity of(0, 1, 0)
(same as any pseudo-class)- if two
@scope
selectors have the same specificity, the one with a closer scope root wins- in the example below, the
p
element is white because.light
is closer than.dark
- in the example below, the
<div class="dark">
<div class="light">
<p>
</div>
</div>
@scope (.light) {
p {
color: white;
}
}
@scope (.dark) {
p {
color: white;
}
}
@starting-style
As of November 2024, Firefox doesn't support animating from display: none
- lets you define starting values for an element, which can be used for transitions when the element is first drawn (either from being inserted into the DOM, or moved from
display: none
to visible)@starting-style
rules aren't applied when removing the element
- in this example,
background-color
will transition from transparent to green when the element is inserted into the page
.alert {
transition: background-color 2s;
background-color: green;
@starting-style {
background-color: transparent;
}
}
@supports
- lets you provide a property and value to test for browser support
@supports (color: rgb(from white r g b)) {
/* this browser supports relative color syntax */
}
- can also test for selector support
@supports not (selector(:has(a, b))) {
/* these rules will only apply to browsers that don't support :has() */
}
CSS Nesting
- Supported in all major browsers as of August 2023
- starting nested rules with a type (element) selector without using
&
is currently inconsistent
- starting nested rules with a type (element) selector without using
.foo {
.bar {
/* equivalent to .foo .bar */
}
& .bar {
/* same as above */
}
.bar & {
/* .bar .foo */
}
&.bar {
/* .foo.bar (note the lack of space) */
}
+ .bar {
/* .foo + .bar */
}
div {
/* this will be .foo div, but browser support is inconsistent - use "& div" instead */
}
/* ⛔️ you can't concatenate strings */
&__bar {
/* this won't equal .foo__bar! */
}
/* but you can do this, as long as the type selector comes first (but note the caveat about browser support) */
div& {
/* div.foo */
}
/* you can use & multiple times */
&:nth-child(1 of &) {
/* matches the first .foo within its parent */
}
}
Nested @rules
- you can nest media queries and other @rules, and leave out the selector if it's the same
.foo {
color: black;
@media (orientation: landscape) {
/* by default matches the parent selector (.foo) */
color: white;
.bar {
/* matches .foo .bar, but only in landscape */
}
@media (min-width > 1024px) {
/* you can even nest multiple layers of @rules */
/* equivalent to @media (orientation: landscape) and (min-width > 1024px) */
}
}
}
- cascade layers can be nested, which creates a new sub-layer joined with a dot
@layer base {
@layer support {
/* creates a layer called base.support */
}
}
Animating dialogs and popovers
With [[#transition-behavior]]
dialog, [popover] {
transition-property: opacity, display;
transition-duration: 0.25s;
/* when setting `display: none`, keeps the element visible until the end of the transition */
transition-behavior: allow-discrete;
@starting-style {
/* triggers fade in when showing */
opacity: 0;
}
}
dialog:not([open]) {
/* triggers fade out when hiding */
opacity: 0;
}
}
[popover]:not(:popover-open) {
/* triggers fade out when hiding */
opacity: 0;
}
}
Without transition-behavior
- use an animation targeting the
[open]
attribute - to animate closing (ex. for a fade out):
- when the close button is clicked, apply a class to the dialog that triggers an animation
- just reversing the animation that plays on open won't work, since it's still the same animation name
- set a one-time
animationend
listener that callsdialog.close()
when the animation ends
- when the close button is clicked, apply a class to the dialog that triggers an animation
function closeModal() {
dialogEl.addEventListener('animationend', () => {
dialog.close()
dialog.classList.remove('fade-out')
}, { once: true })
dialog.classList.add('fade-out')
}
Other
Styling elements with multiple states
- if an element can be in one of multiple exclusive states (for example, a dialog with normal, alert, or error states), use
data-
attributes instead of classes, to avoid the possibility of applying multiple states and causing style conflicts- the default styles for when the data attribute isn't present should reflect the default state
Custom property resolution
- The browser first determines the cascaded value for an element, then "throws away" all other possible values, before checking if the cascaded value is valid
- this means if a custom property value is invalid, it won't fall back to a less specific value
<div class="card">
<p>Lorem ipsum dolor sit amet</p>
</div>
html {
color: red;
}
/* When calculating the `color` value for <p>, this gets thrown away because there is a more specific selector (.card p) */
p {
color: blue;
}
.card {
--color: #notacolor;
}
/* This gets selected as the cascaded value for `color`, but the actual value is invalid! But since the value on `p` was already thrown away, the browser falls back to the red color from `html`. */
.card p {
color: var(--color);
}
- Custom property values are computed before inheritance happens
:root {
--spacing: 0.5rem;
--spacing-small: calc(var(--spacing) / 2); /* 0.25rem */
}
.element {
--spacing: 1rem;
/* --spacing-small is still 0.25rem because the value is
calculated at the root level */
}
- when used in custom property values,
!important
only applies when matching custom property values against each other, not against regular rules
div {
--color: red !important;
--color: blue;
/* red wins even though blue was declared later */
color: var(--color);
}
div {
--color: red !important;
color: var(--color);
/* yellow wins because !important is stripped out after the value of var(--color) is resolved */
color: yellow;
}
unset vs. revert
unset
uses the property's inherited value if it inherits, or its initial value if notrevert
uses the element's default style (as set by the browser), unless the user has applied a custom style, in which case it uses that value- In practice, most methods of applying custom styles (such as Stylus) apply them to the
author
style origin, sorevert
will roll back to the browser's style anyway
- In practice, most methods of applying custom styles (such as Stylus) apply them to the
For example, the default value of the display
property is inline
, but browsers set <div>
elements to display: block
.
Setting display: unset
on a <div>
will apply display: inline
, which probably isn't what you want. Setting display: revert
instead will apply display: block
.
opacity: 0 vs. visibility: hidden
- Elements with
opacity: 0
are still:- clickable (will fire click events)
- focusable
- visible to screen readers
- Elements with
visibility: hidden
(or content-visibility: hidden) don't do any of the above.
Intrinsic sizing keywords (min-content, fit-content, max-content)
min-content
: soft-wrap content as much as possible (fit to the longest word/child)fit-content
: fit to the container, but never smaller thanmin-content
or larger thanmax-content
max-content
: don't wrap content at all, even if it causes overflow
Font-based length units (ex, cap, ch, lh)
ex
: height of a lowercase letter in the current font, usually (not always) ~0.5emcap
: height of a capital letter in the current font- useful for sizing icons inline with text
ch
: inline size of the character0
in the current fontic
: inline size of the character 水 (size of full width ideographse of full width ideographs)lh
: the computed line height- good for margins in sections of text
- root versions of all:
rlh
, etc.
System color keywords
- use colors provided by the operating system
- browser support is inconsistent, so use [[#@supports]] to test and set fallback values appropriately
AccentColor
andAccentColorText
aren't supported in Chromium as of January 2025
Keyword | Example | Description |
---|---|---|
AccentColor |
Background of accented user interface controls | |
AccentColorText |
Text of accented user interface controls | |
ActiveText |
Text of active links | |
ButtonBorder |
Base border color of controls | |
ButtonFace |
Background color of controls | |
ButtonText |
Text color of controls | |
Canvas |
Background of application content or documents | |
CanvasText |
Text color in application content or documents | |
Field |
Background of input fields | |
FieldText |
Text in input fields | |
GrayText |
Text color for disabled items (e.g. a disabled control) | |
Highlight |
Background of selected items | |
HighlightText |
Text color of selected items | |
LinkText |
Text of non-active, non-visited links | |
Mark |
Background of text that has been specially marked (such as by the HTML mark element) | |
MarkText |
Text that has been specially marked (such as by the HTML mark element) | |
VisitedText |
Text of visited links |
Negative transition/animation-delays
A negative transition-delay
or animation-delay
will start the transition/animation partway through. For example, transition-delay: -50ms
will start the transition at the 50ms mark.
Make textareas resize to fit their content
Sync the textarea's contents to a data attribute on the wrapper:
<!-- vanilla example -->
<div class="grow-wrap" data-replicated-value="">
<textarea onInput="this.parentNode.dataset.replicatedValue = this.value"></textarea>
</div>
<!-- Svelte example -->
<div class="grow-wrap" data-replicated-value={input}>
<textarea bind:value={input}></textarea>
</div>
Style the wrapper the same as the textarea, overlay them using grid
, and make the wrapper invisible
.grow-wrap {
display: grid;
}
.grow-wrap textarea,
.grow-wrap::after {
/* IMPORTANT: make sure the textarea and ::after element
are styled exactly the same so they line up */
white-space: pre-wrap;
resize: none;
overflow: hidden;
/* position the textarea and ::after element in the same space */
grid-area: 1 / 1 / 2 / 2;
}
.grow-wrap::after {
content: attr(data-replicated-value) ' ';
visibility: hidden;
pointer-events: none;
}
Styling for print
- use @media print to restyle or hide elements when printing
- preview print rules in Chrome devtools -> Rendering -> Emulate CSS media type
- use [[#@page]] rules to adjust the page margins
- use [[#print-color-adjust]] to prevent the browser from changing the appearance of elements when printing
- use [[#break-before, break-inside, break-after]] to control where pages break
- use
orphans
andwidows
to change the minimum number of lines that can be alone at the bottom/top of a page (both default to 2)