CSS Syntax
CSS syntax is the set of grammar rules that tell a browser how to read a stylesheet: which characters group together as a selector, where a declaration begins and ends, and how properties and values pair up. Every CSS rule you will ever write, from a single color change to a full responsive layout, follows the exact same handful of syntax rules described here. Understanding this syntax precisely, rather than just pattern-matching examples, is what lets you read any stylesheet, spot typos instantly, and write CSS that a browser parses exactly the way you intend.
Overview: How CSS Syntax Works
A CSS stylesheet is a sequence of rules (also called rulesets). Each rule pairs a selector, a pattern that matches one or more elements in the HTML document, with a declaration block wrapped in curly braces { }. Inside the declaration block sits one or more declarations. Each declaration is a property and a value separated by a colon : and terminated by a semicolon ;.
When a browser loads a stylesheet, it does not run CSS like a program with a sequence of instructions; it parses the text into a tree-like data structure called the CSSOM (CSS Object Model). Parsing happens in layers: the raw characters are first split into tokens (identifiers, strings, numbers, punctuation, and so on), those tokens are grouped into rules by watching for { and }, and each rule’s contents are grouped into individual declarations by watching for ;. This tokenizing approach is why CSS syntax is so consistent: a class selector, an attribute selector, and a media query all get tokenized by the same underlying rules.
Whitespace (spaces, tabs, newlines) between tokens is not meaningful in CSS the way it can be in some languages; you can write an entire rule on one line or spread it across many, and the browser interprets it identically. What does matter is punctuation: missing a semicolon, a colon, or a closing brace changes how the parser groups the following text, often silently breaking rules you did not intend to touch. Property names and most keyword values are case-insensitive (COLOR and color behave the same), but selectors that reference HTML class or id attribute values are case-sensitive, because the browser compares them literally against the attribute values in the markup.
Comments use /* ... */ and can span multiple lines; CSS has no single-line // comment syntax, and text inside a comment is completely ignored by the parser, including anything that looks like a declaration. Besides ordinary rules, a stylesheet can contain at-rules, statements that begin with @ such as @media, @import, or @font-face, which either wrap other rules in a condition or configure something about the stylesheet itself, rather than styling elements directly.
One property of CSS syntax that surprises newcomers coming from stricter languages is its error recovery model. If the browser encounters a declaration it cannot understand, an unknown property, an invalid value, a typo, it simply discards that one declaration and moves on to the next; it does not throw an error or stop processing the rest of the stylesheet. This makes CSS forgiving but also means mistakes can hide in plain sight: your page still renders, just without the style you expected. Once the CSSOM is built, the browser walks the HTML document tree and, for every element, works out which declarations from which matching rules apply and win (that resolution process, governed by the cascade and specificity, is covered in its own lesson) before computing final values and painting pixels to the screen.
Syntax
The general shape of a CSS rule looks like this:
selector {
property: value;
property: value;
}
| Part | Example | Purpose |
|---|---|---|
| Selector | .card, h1, #nav a |
Chooses which HTML elements the rule applies to. |
| Declaration block | { ... } |
Curly braces containing every declaration for that selector. |
| Property | color, margin |
The style aspect being set. |
| Value | red, 16px, 1px solid #000 |
What the property is set to; some values are themselves space-separated lists. |
| Colon | : |
Separates a property from its value inside a declaration. |
| Semicolon | ; |
Ends a declaration; required between declarations, optional (but recommended) after the last one. |
| Comment | /* note */ |
Ignored by the parser; documents the stylesheet. |
| At-rule | @media (min-width: 600px) { ... } |
A conditional or configuration statement rather than a plain style rule. |
Examples
Example 1: A single, simple rule
Given the HTML <p>Hello world</p>, the following rule targets every <p> element on the page:
p {
color: #1a1a1a;
font-size: 18px;
}
Result: Every paragraph’s text renders in a near-black color (#1a1a1a) at 18 pixels tall, instead of the browser’s default black, roughly-16px text.
This is the smallest complete rule you can write: one selector, one declaration block, two declarations, each ending in a semicolon. Note that font-size and color are independent declarations; removing either one leaves the other fully functional, because each is parsed and applied separately.
Example 2: Grouping selectors and using a comment
/* Base heading styles */
h1,
h2,
h3 {
font-family: "Georgia", serif;
color: #22313f;
margin-bottom: 0.5em;
}
Result: Every <h1>, <h2>, and <h3> on the page renders in the Georgia serif font, in a dark slate-blue color, with half an em of space below each heading.
The comma between h1, h2, and h3 is a selector list: it tells the browser “apply this exact same declaration block to elements matching any of these selectors,” avoiding three separate, duplicated rules. The comment above the rule is purely for humans reading the stylesheet; the parser strips it out before building the CSSOM.
Example 3: A realistic component with custom properties and a media query
:root {
--card-radius: 8px;
--card-bg: #ffffff;
}
.card {
background: var(--card-bg);
border: 1px solid #d0d7de;
border-radius: var(--card-radius);
padding: 16px 20px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}
@media (min-width: 600px) {
.card {
padding: 24px 32px;
}
}
Result: Any element with class="card" renders as a white rounded rectangle with an 8px border radius, a thin light-gray border, a soft drop shadow, and 16px/20px of inner padding. Once the browser window is at least 600px wide, the padding grows to 24px/32px, giving the card more breathing room on larger screens.
This example shows several syntax features working together: a :root rule defining custom properties (variables) with the --name syntax, the var() function consuming those variables as values, a shorthand property (border setting width, style, and color in one declaration), and an @media at-rule that wraps an entire nested rule inside its own braces. Custom properties and modern functions like var() are supported by all current evergreen browsers, but note that very old browsers (like IE11) do not understand them and will simply ignore those declarations, another example of CSS’s error-recovery behavior in action.
Under the Hood: How the Browser Parses a Stylesheet
It helps to walk through what actually happens, step by step, when the rendering engine processes CSS text:
1. Tokenizing
The raw stylesheet text is scanned character by character and grouped into tokens: identifiers (like color or card), punctuation ({, }, :, ;, ,), strings, numbers, and dimensions (a number fused to a unit, like 16px). This tokenizing stage is defined by the CSS Syntax Module and is shared by every part of CSS, from selectors to at-rules.
2. Parsing rules
The parser reads tokens until it hits a {; everything before that becomes the selector (or selector list). It then reads declarations until the matching }, splitting on ; to separate one declaration from the next, and on the first : within a declaration to separate the property from the value.
3. Validating declarations
Each property/value pair is checked against that property’s grammar. If the value doesn’t match what the property expects (for example, a keyword the property doesn’t recognize), the whole declaration is dropped, but parsing continues normally with the next declaration. Nothing else in the stylesheet is affected.
4. Building the CSSOM
Valid rules are compiled into the CSS Object Model, a structured representation the browser can query efficiently, similar in spirit to how HTML becomes the DOM.
5. Matching and cascading
For every element in the DOM, the browser finds every rule whose selector matches that element, then uses the cascade (origin, importance, specificity, and source order) to decide which declaration wins for each property.
6. Computing and painting
Winning values go through further resolution, specified value to computed value (resolving keywords and relative units) to used value (resolving percentages and “auto” once layout is known), before the engine finally paints pixels to the screen.
Common Mistakes
Mistake 1: Forgetting a semicolon
Leaving out a semicolon doesn’t just affect one property, it can corrupt the declaration that follows, because the parser hasn’t found the boundary it expects yet:
.card {
color: red
background: white;
}
Because there is no ; after red, the parser cannot cleanly separate the color declaration from what comes next, and this typically causes the whole declaration to be treated as invalid and dropped. The fix is to always terminate every declaration with a semicolon:
.card {
color: red;
background: white;
}
Mistake 2: Using semicolons instead of commas to group selectors
Selector lists are separated by commas, not semicolons. Semicolons only belong inside a declaration block:
h1; h2; h3 {
color: navy;
}
This is not a valid selector list, the parser does not know how to interpret h1; h2; h3 as “three selectors,” and the entire rule is likely to be discarded, meaning none of the headings get styled. The correct syntax uses commas:
h1, h2, h3 {
color: navy;
}
Best Practices
- Always end every declaration with a semicolon, including the last one in a block, so adding a new declaration later never breaks the one above it.
- Put one declaration per line and indent consistently; CSS ignores whitespace, but consistent formatting makes mistakes far easier to spot.
- Use a comment to label sections of a large stylesheet (
/* Navigation */,/* Buttons */) so you and future readers can navigate it quickly. - Prefer grouping selectors with commas over duplicating identical declaration blocks for multiple selectors.
- Run a CSS linter (such as Stylelint) in your editor or build process; it catches missing semicolons, unclosed braces, and invalid values before they ever reach the browser silently.
- Remember that unknown or invalid declarations fail silently, if a style “isn’t working,” check your browser’s DevTools for a strikethrough on that declaration, which indicates the parser rejected or overrode it.
- Keep custom property names and other identifiers lowercase and hyphenated (
--card-radius, not--CardRadius) for consistency, even though CSS itself doesn’t require it.
Practice Exercises
Exercise 1: Write a single rule that sets the <body> element’s background color to a light gray (#f4f4f4) and its font-family to a sans-serif stack of your choice.
Exercise 2: The following stylesheet has a syntax mistake. Identify it and rewrite the rule correctly.
.alert {
color: darkred
font-weight: bold;
}
Hint: Look closely at the end of the first declaration.
Exercise 3: Using a single selector list, give <h1>, <h2>, and <h3> a shared margin-bottom of 0.5em. Then write a separate rule for <p> that sets line-height to 1.6.
Summary
- A CSS rule pairs a selector with a declaration block made of one or more property/value declarations.
- Declarations use a colon between property and value, and end with a semicolon.
- Whitespace is not meaningful, but punctuation (
{ },:,;,,) defines the structure the parser relies on. - Comments use
/* ... */and are stripped out before the browser builds the CSSOM. - Comma-separated selector lists let one declaration block apply to multiple selectors at once.
- At-rules like
@mediaand@importuse the same tokenizing rules but configure or condition styles rather than applying them directly. - CSS parsing is forgiving: invalid declarations are dropped silently rather than breaking the whole stylesheet, which makes DevTools essential for catching mistakes.
- Class and ID selectors are case-sensitive because they are matched against literal HTML attribute values; most property names and keywords are not.
