Repository navigation
Styling
Everything between the stylesheet's bytes and a computed style for each element.
The CSS tokenizer and parser follow css-syntax-3 (the current draft, nesting-era block contents), with a unicode-range gate. Both are fuzzed.
Every attribute matcher, the four combinators, :not(), :is(), :where(), the structural pseudo-classes with the full an+b grammar, and specificity. :has() takes a relative selector list: each of its selectors is written internally as an ordinary one with a :scope compound in front, so the right-to-left walk that was already there ends on the element being tested and nowhere else, and the leading combinator says where to look for candidates — inside the element for a descendant or a child, after it among its siblings otherwise, a sibling's own descendants included. :nth-child() and :nth-last-child() take the of S form, counting only the siblings that match S; an element outside that list has no position among them at all, which is not the same as a position of zero. ::before, ::after and ::first-letter are lifted out of the last compound so the rest of the selector matches the originating element and the cascade files the rule under the box it addresses; a pseudo-element anywhere but the end of a selector invalidates it, as the specification says, and one that is not written stays in the compound and matches nothing.
Matching is indexed: rules are bucketed by the rightmost compound's id, class or type, and an ancestor filter (a counting Bloom filter over the compounds to the left of descendant and child combinators) rejects most candidates before they are tried. Wikipedia's 900-rule cascade matches in a few milliseconds.
- Origins and importance: the UA sheet, author sheets, style attributes;
!important; specificity; order. - Inheritance, and the CSS-wide keywords
inherit,initialandunset(andrevert, read asunset) on every property the engine knows, on shorthands, and onall. -
font-sizeis settled first, in cascade order, soemand the font-relative units in the same element's other declarations resolve against it. - A border color that was never written is
currentColorand inherits as that keyword — the child's own color — not as the parent's resolved color.
- Lengths:
px,em,rem,ex,ch,pt,pc,in,cm,mm,Q,vw,vh,vmin,vmax, and percentages.exis the x-height of the first available face andchthe advance of its "0", read from the face itself — half the font size only where a face does not say. The family, weight and slant are settled before any length is, so those two measure the face the element ends up with. -
calc(),min(),max()andclamp()over lengths and percentages. A mixed sum such ascalc(100% - 20px)stays pixels plus a percentage until layout resolves it against its containing block. - Colors: hex (3, 4, 6 and 8 digits),
rgb()/rgba()andhsl()/hsla()in the modern and the legacy syntax,transparent,currentcolor, and all 148 CSS named colors.
--name declarations cascade and inherit like any property; var() with a fallback is substituted anywhere in a value, inside functions and blocks included. Cycles and references with no value and no fallback drop the declaration, as the specification says.
-
<style>sheets (a<![CDATA[wrapper is unwrapped),<link rel="stylesheet">, and@importahead of its sheet, bounded in depth and count. - The charset rules: a byte order mark, the HTTP
Content-Typecharset,@charset, then UTF-8. - External sheets and images load through the page's own session, with the page as first party, so third-party cookies stay out.
- Media queries (MQ4): types,
not/only/and/or, width and height in the plain and the range syntax, orientation, aspect ratio, resolution, preference and pointer features — evaluated against the real viewport, with@mediablocks,<link media>and@importconditions all gated, and the shell restyling on resize.
A component draws itself in a shadow tree, and the styles follow the tree (CSS Scoping, Shadow Parts).
- A tree's sheets — its
<style>elements and the sheets its root adopted — apply inside it and nowhere else; the document's sheets do not reach in. The built-in sheet reaches everywhere. - Each kind of tree has one compiled rule set, made when a resolution first meets it and kept by what its sheets say: a hundred instances of one component compile their sheet once.
-
:host,:host(…)and:host-context(…)match the host from inside, and nothing else in the tree's sheets can: the host is featureless there.::slotted(…)styles what a slot takes;::part(…), with states after it (::part(tab):hover), styles a part from outside. - Declarations from different trees are ordered by tree before layer and specificity: the outer tree wins for normal declarations, the inner for important ones.
- Inheritance and layout follow the flat tree: a host's place is taken by its shadow tree, a
<slot>'s by what it was assigned or by its own children when it was assigned nothing.slotisdisplay: contents. - The incremental restyle walks the same tree; a change to a tree's sheet restyles that tree and what it shows, and nothing else.
Things move as Web Animations, CSS Animations and CSS Transitions have them, and the three are one model: a CSS animation and a transition are animations a script can see, pause, seek and cancel like any other.
- From script:
element.animate(),Animation,KeyframeEffect,DocumentTimeline,getAnimations()on an element and on the document,commitStyles()andpersist(), thefinishedandreadypromises, and thefinish,cancelandremoveevents; keyframes as a list or as one object of lists, composited byreplace,addoraccumulate; an animation that later ones wholly cover is removed once nothing sees it. - From a sheet: the
animation-*properties and@keyframes(each shadow tree has its own), with theanimationstart,animationiteration,animationendandanimationcancelevents; thetransition-*properties — a transition starts when a property changes on a box that was drawn before, and one reversed partway is shortened as the standard says — with thetransitionrun,transitionstart,transitionendandtransitioncancelevents. - Easing: the keywords,
cubic-bezier(),steps()with every jump position, andlinear(). - In the cascade an animation's values sit above the page's normal declarations and below its important ones, and a transition's above everything.
- Frames: while anything wants one, the window runs a rendering update on a 60 Hz grid — the animations advance and fire their events, the
requestAnimationFramecallbacks run with one shared timestamp, and the page is restyled once. A page that animates nothing asks for no frames. - What animates is what the engine computes: colours, lengths, percentages, numbers, opacity, translations and shadow lists (padded with transparent shadows to meet, an inset shadow meeting only an inset one, added one list after the other) among the rest. Filters and transforms beyond a translation are not drawn yet (Not written yet).
@font-face with TrueType sources (format("truetype"), format("opentype"), format("woff"), format("woff2"), or an unlabeled source that is not EOT or SVG): the file is fetched through the page's session, unwrapped when it is a WOFF or a WOFF2 (the glyph outlines, their offsets and the horizontal metrics rebuilt from the forms WOFF2 transforms them into), parsed by the TrueType reader, and answers ahead of the machine's fonts for its family, weight, stretch and slant.
The descriptors a face is matched by: font-weight and font-stretch, each a value or a range, and font-style; unicode-range keeps a face to its code points, so a family declared in several pieces answers character by character. Matching follows css-fonts-4 §5.2 — stretch first, then style, then weight, the nearer side of the wanted value before the farther — and a page face is tried ahead of the machine's for its family. The first available font of a stack is the first face whose unicode-range has the space in it; line metrics and the ex and ch units come from it, and a line whose glyphs fall back to other faces takes their ascent and descent into line-height: normal.
A font is fetched when the page's text first needs it, not when a sheet declares it. A face stands in its family from the moment it is declared, and the page is asked for it by the first code point that gets as far as it — one in its unicode-range, with no face ahead of it in the stack to draw it — or by the first read of the line's metrics (or of an ex or ch length) that names it as the first available font. So a family declared in a dozen unicode-range pieces is fetched piece by piece as its text calls for them, a weight no text is set in is not fetched, and neither is a family nothing uses. Where the bytes are at hand — the cache, a local file — the text is set in the font from that layout on; where they have to cross the network the page is not held: the text is laid out in the next face of its stack, and again in the font when it has come. document.fonts.load() asks for the faces its font and text name, which is how a page has a font there before it draws with it on a canvas, where no layout would ask. SASHFOLD_LAZY=0 in the environment fetches every declared font as the sheets are read, to compare with; --report says how many fonts a page declared and how many were fetched.
Box model (display with the two-value syntax and every table value, width, height, min-*, max-*, margins, padding, borders as shorthands and every side's width, style and color longhand, border-radius as the shorthand — one to four radii on each side of the slash — and the four corner longhands), float, clear, overflow (per axis, clip kept apart from the values that scroll), positioning (position, the four offsets, inset, z-index), visibility, opacity, transform/translate (translations).
Flexbox (flex-*, justify-content, align-*, gap, order) and grid (grid-template-* with repeat() and named lines, grid-template-areas, the grid/grid-template shorthands, grid-auto-*, grid-row/grid-column/grid-area and their longhands, justify-items/justify-self, place-*).
Tables (border-collapse, border-spacing, caption-side, empty-cells, table-layout), text (color, font-* and the font shorthand, line-height, vertical-align, direction (and the dir attribute, auto included), unicode-bidi, text-align (start, end, match-parent and justify-all included), text-align-last, text-justify, text-indent, letter-spacing, word-spacing, white-space, text-transform, text-decoration), the list properties (list-style-type with the numbering systems — decimal, decimal-leading-zero, roman, alphabetic, greek, armenian, georgian , list-style-position and the list-style shorthand), the counter properties (counter-reset, counter-increment, counter-set), content and quotes.
Backgrounds: background-color, background-image with url() and the gradient functions, background-repeat (repeat, space, round, no-repeat, per axis), background-position, background-size, background-origin, background-clip, and the background shorthand with its layers.
Flow-relative properties (css-logical-1): a box can name its edges by where its content starts rather than by which way is left — margin-inline-start, padding-block-end, border-inline-start-color, inset-inline-end and the rest of that set, the axis shorthands margin-inline, padding-block, inset-inline, border-inline-width and their kin, border-inline and border-block themselves, and the sizes inline-size, block-size and their min-/max- forms. Each is the physical property it names for that element's own direction, so an inline start is the right edge in a right-to-left element and the block axis is the vertical one whichever way the text reads. The translation happens in cascade order, which is what lets a margin-left written after a margin-inline-start win, and inherit on one of them reaches the physical property it stands for. The user-agent sheet uses them where browsers do, so a right-to-left list keeps its markers on the page.
Values: lengths and percentages, calc()/min()/max()/clamp(), viewport units, custom properties with var() and its fallback, and the CSS-wide keywords inherit, initial, unset and all.
::before and ::after boxes with content strings, attr() (with a fallback), open-quote/close-quote from the quotes property nested by depth in tree order, and counter()/counters(); content: normal/none generate nothing and url() leaves the declaration unapplied. The boxes lay out as inline text, as blocks of their own (the clearfix idiom included), as floats, and as flex items.
Counters follow CSS 2.1 nested scope: a counter-reset makes an instance covering its own box, that box's descendants and its following siblings, so a counter nests itself when a list resets it again; counter-increment and counter-set reach the innermost instance in scope, and a counter nothing has reset reads as zero. A box that is never generated — display: none, and everything inside it — does no counter work, while a visibility: hidden box still counts. counter() takes the innermost value and counters() joins every value in scope with a separator; both take a counter style as their last argument.
::first-letter dresses the first letter of a block container's first line, in either spelling — the one colon CSS 2.1 wrote, or the two of the current draft. The run it selects is the letter, the marks that hang off it, and the punctuation on either side: the Unicode Ps, Pe, Pi, Pf and Po categories, read from tables the Unicode data generates. A word of nothing but punctuation leads into the letter the next word holds and comes along with it. A space the line kept in front of the letter — a no-break, en, em or thin space — is not selected, and nothing is dressed.
The letter need not be written in the block itself: it is looked for through the inline boxes on the line, and when a block holds boxes rather than text, its first line lives inside the first of them and the style is handed down that chain. As the fictional start tag goes inside those inline boxes, an inherited property the ::first-letter rules did not set comes from the box the letter was written in rather than from the block. A flex or grid container is passed by — it holds items, not lines.
Colour, font, weight and size apply; a background, border or padding on the letter alone waits on inline box painting (Not written yet).
A small user-agent stylesheet gives the HTML elements their defaults: block and list-item displays, heading sizes and margins, pre and code in monospace, anchors with an href in blue and underlined, sub and sup raised and lowered at a smaller size, form controls at 13.333 px, and the table displays with cells middle-aligned.
Getting started
What works today
Measured
Design decisions
Testing and tools