CSS Getting Started
Cascading Style Sheets (CSS) is the language used to describe the presentation of a document written in HTML or XML. CSS controls the layout, colors, fonts, spacing, and visual appearance of web pages. It is one of the three core technologies of the web, alongside HTML and JavaScript.
CSS separates content from presentation, allowing you to apply styles across multiple pages from a single stylesheet. This separation improves maintainability, accessibility, and enables powerful responsive design patterns.
Modern CSS has evolved far beyond simple color and font properties. It now includes features like custom properties (variables), Grid, Flexbox, container queries, animations, and color functions like oklch() and color-mix(). The CSS Object Model (CSSOM) works alongside the DOM to represent styles in a structured, programmable way.
| 1 | <!DOCTYPE html> |
| 2 | <html lang="en"> |
| 3 | <head> |
| 4 | <meta charset="UTF-8" /> |
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| 6 | <title>CSS Example</title> |
| 7 | <link rel="stylesheet" href="styles.css" /> |
| 8 | </head> |
| 9 | <body> |
| 10 | <h1>Hello, CSS!</h1> |
| 11 | <p>This paragraph is styled with an external stylesheet.</p> |
| 12 | </body> |
| 13 | </html> |
| 1 | /* External stylesheet: styles.css */ |
| 2 | h1 { |
| 3 | color: #00FF41; |
| 4 | font-family: system-ui, sans-serif; |
| 5 | text-align: center; |
| 6 | } |
| 7 | |
| 8 | p { |
| 9 | font-size: 1.125rem; |
| 10 | line-height: 1.6; |
| 11 | color: #A0A0A0; |
| 12 | max-width: 65ch; |
| 13 | margin: 0 auto; |
| 14 | } |
info
Understanding how a browser processes CSS is essential for debugging and writing performant stylesheets. The rendering pipeline follows six distinct stages:
The browser parses the HTML and constructs the DOM (Document Object Model). When it encounters a <link> or <style> tag, it fetches and parses the CSS to build the CSSOM (CSS Object Model). CSS parsing works right-to-left on selectors for performance.
The browser applies the cascade algorithm to resolve conflicting declarations. It considers specificity, importance, source order, and origin (user agent, user, author) to determine which styles win.
The browser resolves all CSS values through four stages: declared values (author styles), cascaded values (after cascade wins), specified values (defaults if cascaded value is missing), computed values (resolving relative units like em/rem to px), used values (final values after layout), and actual values (browser-constrained values).
The browser calculates the geometry of each element: its position and size within the viewport. This involves running the layout algorithm determined by the element's display property (block, inline, flex, grid, etc.).
The browser fills in pixels: colors, backgrounds, borders, shadows, text, and images. Each element is painted onto one or more layers. Properties like transform and opacity are compositor-only and skip paint, making them fast to animate.
Individual painted layers are composited together into the final image displayed on screen. This stage is GPU-accelerated. Promoting elements to their own layer (via will-change or transform: translateZ(0)) can improve performance.
best practice
CSS is composed of rule sets (also called rule blocks) and at-rules. A rule set consists of a selector and a declaration block. Each declaration pairs a property with a value.
Rule Set Structure
| 1 | selector { |
| 2 | property: value; |
| 3 | property: value; |
| 4 | } |
| 5 | /* ↑ */ |
| 6 | /* declaration block */ |
A concrete example:
| 1 | /* Selector: targets all <p> elements */ |
| 2 | p { |
| 3 | /* Declaration 1 */ |
| 4 | color: #E0E0E0; |
| 5 | |
| 6 | /* Declaration 2 */ |
| 7 | font-size: 1rem; |
| 8 | |
| 9 | /* Declaration 3 */ |
| 10 | line-height: 1.6; |
| 11 | } |
| 12 | |
| 13 | /* Multiple selectors can be grouped */ |
| 14 | h1, h2, h3 { |
| 15 | font-family: "Inter", system-ui, sans-serif; |
| 16 | font-weight: 600; |
| 17 | } |
At-Rules
At-rules start with @ and instruct CSS how to behave. Common at-rules include:
| At-Rule | Purpose |
|---|---|
| @import | Import an external stylesheet |
| @media | Conditional styles based on media features |
| @font-face | Define custom fonts |
| @keyframes | Define animation sequences |
| @property | Register typed custom properties |
| @container | Container query conditions |
| @layer | Control cascade layer order |
| @scope | Limit selector scope |
| 1 | @import url("reset.css"); |
| 2 | |
| 3 | @font-face { |
| 4 | font-family: "Geist"; |
| 5 | src: url("fonts/Geist.woff2") format("woff2"); |
| 6 | } |
| 7 | |
| 8 | @media (max-width: 768px) { |
| 9 | body { |
| 10 | font-size: 0.875rem; |
| 11 | } |
| 12 | } |
| 13 | |
| 14 | @keyframes fadeIn { |
| 15 | from { opacity: 0; transform: translateY(8px); } |
| 16 | to { opacity: 1; transform: translateY(0); } |
| 17 | } |
| 18 | |
| 19 | @property --brand-color { |
| 20 | syntax: "<color>"; |
| 21 | inherits: false; |
| 22 | initial-value: #00FF41; |
| 23 | } |
warning
Selectors define which elements a set of styles applies to. CSS offers a rich selector syntax that allows precise element targeting.
Basic Selectors
| Selector | Example | Description |
|---|---|---|
| Type | h1 | Matches all <h1> elements |
| Class | .card | Matches elements with class="card" |
| ID | #header | Matches element with id="header" (use sparingly) |
| Attribute | [type="text"] | Matches elements with a specific attribute |
| Universal | * | Matches all elements |
| 1 | /* Type selector */ |
| 2 | article { padding: 2rem; } |
| 3 | |
| 4 | /* Class selector */ |
| 5 | .card { |
| 6 | background: #0D0D0D; |
| 7 | border-radius: 8px; |
| 8 | } |
| 9 | |
| 10 | /* ID selector — avoid when possible, prefer classes */ |
| 11 | #main-nav { position: sticky; top: 0; } |
| 12 | |
| 13 | /* Attribute selectors */ |
| 14 | [disabled] { opacity: 0.5; cursor: not-allowed; } |
| 15 | [data-type="primary"] { background: #00FF41; } |
| 16 | [href^="https"] { color: #00FF41; } |
| 17 | [class*="btn-"] { font-weight: 500; } |
| 18 | |
| 19 | /* Universal selector */ |
| 20 | * { box-sizing: border-box; } |
Combinators
Combinators express relationships between selectors:
| Combinator | Symbol | Example | Description |
|---|---|---|---|
| Descendant | (space) | article p | <p> anywhere inside <article> |
| Child | > | article > p | <p> as a direct child of <article> |
| Adjacent sibling | + | h2 + p | <p> immediately after <h2> |
| General sibling | ~ | h2 ~ p | <p> anywhere after <h2> (same parent) |
| Column | || | col || td | <td> that belongs to a column |
| 1 | /* Descendant — any nested level */ |
| 2 | article p { color: #A0A0A0; } |
| 3 | |
| 4 | /* Child — only direct children */ |
| 5 | nav > ul { display: flex; gap: 1rem; } |
| 6 | |
| 7 | /* Adjacent sibling — immediately after */ |
| 8 | h2 + p { margin-top: 0; font-size: 1.125rem; } |
| 9 | |
| 10 | /* General sibling — any after */ |
| 11 | h2 ~ p { color: #808080; } |
| 12 | |
| 13 | /* Chaining combinators */ |
| 14 | article.content > section > p:first-of-type { |
| 15 | font-size: 1.25rem; |
| 16 | font-weight: 500; |
| 17 | } |
Pseudo-Classes
Pseudo-classes select elements based on their state or position:
1/* Dynamic user state */2button:hover { background: #4338CA; }3input:focus { outline: 2px solid #00FF41; }4a:active { color: #00FF41; }5a:visited { color: #888; }6 7/* Position-based */8li:first-child { font-weight: bold; }9li:last-child { border-bottom: none; }10li:nth-child(odd) { background: #0D0D0D; }11li:nth-child(3n+1) { color: #00FF41; }12 13/* Functional pseudo-classes */14button:not(.primary) { opacity: 0.8; }15:is(h1, h2, h3) { line-height: 1.3; }16:where(.card, .panel) { padding: 1.5rem; }17form:has(input:invalid) { border-color: #EF4444; }18 19/* Form state */20input:checked { accent-color: #00FF41; }21input:disabled { opacity: 0.4; }22input:required { border-color: #00FF41; }Pseudo-Elements
Pseudo-elements style specific parts of an element. They use the double-colon :: syntax:
| 1 | /* Generated content */ |
| 2 | .element::before { |
| 3 | content: "→ "; |
| 4 | color: #00FF41; |
| 5 | } |
| 6 | |
| 7 | .element::after { |
| 8 | content: ""; |
| 9 | display: block; |
| 10 | width: 100%; |
| 11 | height: 2px; |
| 12 | background: linear-gradient(90deg, #00FF41, transparent); |
| 13 | } |
| 14 | |
| 15 | /* Typography pseudo-elements */ |
| 16 | p::first-line { |
| 17 | font-size: 1.25em; |
| 18 | font-weight: 600; |
| 19 | color: #E0E0E0; |
| 20 | } |
| 21 | |
| 22 | p::first-letter { |
| 23 | font-size: 3em; |
| 24 | float: left; |
| 25 | line-height: 0.8; |
| 26 | margin-right: 0.25rem; |
| 27 | } |
| 28 | |
| 29 | /* Selection styling */ |
| 30 | ::selection { |
| 31 | background: #00FF41; |
| 32 | color: #0A0A0A; |
| 33 | } |
| 34 | |
| 35 | /* Placeholder */ |
| 36 | input::placeholder { |
| 37 | color: #525252; |
| 38 | font-style: italic; |
| 39 | } |
pro tip
The cascade is the algorithm that resolves conflicting CSS declarations. When two rules apply to the same element, the browser uses specificity, importance, and source order to determine which wins.
Specificity Hierarchy
Specificity is calculated as a four-part value (inline, ID, class, element). Each selector type contributes to one of these categories:
| Level | Selector Type | Example | Weight |
|---|---|---|---|
| A | Inline styles | style="..." | 1, 0, 0, 0 |
| B | IDs | #header | 0, 1, 0, 0 |
| C | Classes, attributes, pseudo-classes | .card [type] :hover | 0, 0, 1, 0 |
| D | Elements and pseudo-elements | div ::before | 0, 0, 0, 1 |
Specificity Calculator Examples
| Selector | Inline | ID | Class | Element | Total |
|---|---|---|---|---|---|
| p | 0 | 0 | 0 | 1 | 0,0,0,1 |
| .card | 0 | 0 | 1 | 0 | 0,0,1,0 |
| #header .nav a | 0 | 1 | 1 | 1 | 0,1,1,1 |
| ul li:first-child | 0 | 0 | 1 | 2 | 0,0,1,2 |
| style="color: red;" | 1 | 0 | 0 | 0 | 1,0,0,0 |
| #app .sidebar .nav a:hover | 0 | 1 | 3 | 1 | 0,1,3,1 |
The !important Exception
Adding !important to a declaration elevates it above all normal declarations, regardless of specificity. Use it sparingly — it breaks the cascade and makes debugging difficult.
| 1 | /* Without !important */ |
| 2 | #header .title { color: red; } /* specificity: 0,1,1,0 → wins */ |
| 3 | .title { color: blue; } /* specificity: 0,0,1,0 → loses */ |
| 4 | |
| 5 | /* With !important */ |
| 6 | .title { color: blue !important; } /* overrides the above ID selector */ |
| 7 | #header .title { color: red; } /* loses to !important */ |
| 8 | |
| 9 | /* Cascade order tips */ |
| 10 | /* 1. !important wins over everything */ |
| 11 | /* 2. Higher specificity wins */ |
| 12 | /* 3. If specificity is equal, the last declaration wins */ |
warning
Cascade Layers
The @layer at-rule gives you explicit control over cascade order, regardless of specificity or source order:
| 1 | /* Define layer order — first defined = lowest priority */ |
| 2 | @layer reset, base, components, utilities; |
| 3 | |
| 4 | /* Styles in later layers override earlier layers */ |
| 5 | @layer reset { |
| 6 | h1 { margin: 0; } |
| 7 | } |
| 8 | |
| 9 | @layer components { |
| 10 | .card { padding: 1.5rem; } |
| 11 | } |
| 12 | |
| 13 | @layer utilities { |
| 14 | .p-4 { padding: 1rem !important; } |
| 15 | } |
| 16 | |
| 17 | /* Unlayered styles always win over layered styles */ |
Some CSS properties inherit from their parent element, while others do not. Understanding inheritance is key to writing concise, maintainable stylesheets.
Inherited vs Non-Inherited
| Inherited | Not Inherited |
|---|---|
| color | margin |
| font-family | padding |
| font-size | border |
| line-height | display |
| text-align | width / height |
| visibility | background |
| cursor | position |
| letter-spacing | box-shadow |
The Cascade Keywords
Every CSS property accepts these universal keywords for controlling inheritance behavior:
| Keyword | Behavior |
|---|---|
| inherit | Forces the property to inherit from its parent (even if normally non-inherited) |
| initial | Resets the property to its CSS specification default value |
| unset | Acts as inherit if the property is normally inherited, otherwise as initial |
| revert | Resets to the user-agent stylesheet default (or the cascade's natural state) |
| revert-layer | Resets to the value from a previous cascade layer |
| 1 | /* Inheritance in action */ |
| 2 | body { |
| 3 | color: #E0E0E0; /* inherited by all text elements */ |
| 4 | font-family: system-ui; /* inherited */ |
| 5 | } |
| 6 | |
| 7 | /* Force inheritance on a non-inherited property */ |
| 8 | button { |
| 9 | border: inherit; /* normally not inherited */ |
| 10 | background: inherit; |
| 11 | } |
| 12 | |
| 13 | /* Reset to initial value */ |
| 14 | .reset-box { |
| 15 | all: initial; /* resets ALL properties to initial values */ |
| 16 | } |
| 17 | |
| 18 | /* Use unset for resetting */ |
| 19 | .link-list { |
| 20 | margin: unset; /* margin is not inherited → acts as initial (0) */ |
| 21 | color: unset; /* color is inherited → acts as inherit */ |
| 22 | } |
| 23 | |
| 24 | /* Revert to browser default */ |
| 25 | .no-style { |
| 26 | all: revert; |
| 27 | } |
pro tip
Every element in CSS is rendered as a rectangular box. The box model describes how the content, padding, border, and margin areas interact to determine an element's total size.
Content-Box vs Border-Box
The box-sizing property changes how width and height are calculated:
| Box Model | width Includes | Total Width Formula |
|---|---|---|
| content-box | Content only | width + padding + border + margin |
| border-box | Content + padding + border | width + margin |
| 1 | /* The universal reset — always use border-box */ |
| 2 | *, *::before, *::after { |
| 3 | box-sizing: border-box; |
| 4 | } |
| 5 | |
| 6 | /* content-box (default) — width excludes padding/border */ |
| 7 | .content-box { |
| 8 | box-sizing: content-box; |
| 9 | width: 200px; |
| 10 | padding: 20px; |
| 11 | border: 2px solid #00FF41; |
| 12 | /* total width = 200 + 40 + 4 = 244px */ |
| 13 | } |
| 14 | |
| 15 | /* border-box — width includes padding/border */ |
| 16 | .border-box { |
| 17 | box-sizing: border-box; |
| 18 | width: 200px; |
| 19 | padding: 20px; |
| 20 | border: 2px solid #00FF41; |
| 21 | /* total width = 200px (content area = 200 - 40 - 4 = 156px) */ |
| 22 | } |
Margin Collapse
Vertical margins between adjacent block elements collapse into a single margin equal to the larger of the two margins. This only applies to vertical (margin-top, margin-bottom) margins in the same block formatting context.
| 1 | /* Margin collapse example */ |
| 2 | .box-a { margin-bottom: 40px; } |
| 3 | .box-b { margin-top: 30px; } |
| 4 | /* The gap between box-a and box-b is 40px (not 70px) */ |
| 5 | |
| 6 | /* Margin collapse does NOT apply to: */ |
| 7 | /* - Flex children */ |
| 8 | /* - Grid children */ |
| 9 | /* - Elements with overflow: hidden/auto/scroll */ |
| 10 | /* - Absolutely positioned elements */ |
| 11 | /* - Inline-block elements */ |
Outline vs Border
Outlines are similar to borders but do NOT take up space in the box model. They are drawn outside the border edge and do not affect layout. Use outlines for focus indicators and debugging.
| 1 | /* Border — takes up space, part of box model */ |
| 2 | .card { |
| 3 | border: 2px solid #00FF41; |
| 4 | padding: 16px; /* content pushed inward */ |
| 5 | } |
| 6 | |
| 7 | /* Outline — does NOT take up space */ |
| 8 | .card:focus-within { |
| 9 | outline: 2px solid #00FF41; |
| 10 | outline-offset: 2px; /* drawn outside the border */ |
| 11 | /* no layout shift */ |
| 12 | } |
| 13 | |
| 14 | /* Outline for debugging element bounds */ |
| 15 | .debug * { |
| 16 | outline: 1px solid rgba(255, 0, 0, 0.3) !important; |
| 17 | } |
The display property determines how an element behaves in the layout. It controls whether the element generates a box, and how that box interacts with surrounding elements.
| Value | Behavior | Width Default | Height Default | Margin |
|---|---|---|---|---|
| block | Fills available width, starts on new line | 100% of parent | Content height | All sides respected |
| inline | Flows in text content, no width/height control | Content width | Content height | Horizontal only |
| inline-block | Inline flow but respects width/height/margin | Content width | Content height | All sides |
| none | Removed from layout entirely (not visible) | — | — | — |
| contents | Box disappears, children inherit parent's layout | — | — | — |
| flow-root | Block-level, creates new BFC (contains floats) | 100% of parent | Content height | All sides |
| flex | Block-level flex container | 100% of parent | Content height | All sides |
| grid | Block-level grid container | 100% of parent | Content height | All sides |
info
CSS offers a wide range of units for sizing elements, text, spacing, and more. Choosing the right unit makes your design responsive and accessible.
Unit Comparison Table
| Unit | Type | Relative To | Best For |
|---|---|---|---|
| px | Absolute | Device pixel (1px = 1/96th of 1in) | Borders, shadows, fine details |
| em | Relative | Parent element's font-size | Padding, margins relative to text size |
| rem | Relative | Root element's font-size (usually 16px) | Font sizes, spacing (accessible scaling) |
| % | Relative | Parent element's same property | Widths, heights, font-size |
| vw | Viewport | 1% of viewport width | Full-width sections, hero text |
| vh | Viewport | 1% of viewport height | Full-height sections, overlays |
| vmin | Viewport | 1% of the smaller viewport dimension | Responsive square elements |
| vmax | Viewport | 1% of the larger viewport dimension | Responsive hero sections |
| ch | Relative | Width of the "0" character in the font | Measure / line length (max-width: 65ch) |
| ex | Relative | Height of the "x" character | Inline vertical metrics |
| lh | Relative | Element's own line-height | Vertical rhythm |
| rlh | Relative | Root element's line-height | Global vertical rhythm |
| cap | Relative | Capital letter height of the font | Typography alignment |
| dvh | Viewport | Dynamic viewport height (adjusts for mobile UI) | Mobile full-height sections |
| svh | Viewport | Smallest viewport height | Safe minimum heights |
| lvh | Viewport | Largest viewport height | Maximum height expectations |
Unit Usage Examples
| 1 | /* --- Typography: use rem for accessibility --- */ |
| 2 | html { font-size: 16px; } /* 1rem = 16px default */ |
| 3 | body { font-size: 1rem; } |
| 4 | h1 { font-size: 2.5rem; } /* 40px */ |
| 5 | h2 { font-size: 2rem; } /* 32px */ |
| 6 | p { font-size: 1rem; } /* 16px */ |
| 7 | small { font-size: 0.875rem; } /* 14px */ |
| 8 | |
| 9 | /* --- Spacing: use rem consistently --- */ |
| 10 | .card { |
| 11 | padding: 1.5rem; /* 24px */ |
| 12 | margin-bottom: 2rem; /* 32px */ |
| 13 | border-radius: 0.5rem; /* 8px */ |
| 14 | gap: 0.75rem; /* 12px */ |
| 15 | } |
| 16 | |
| 17 | /* --- Line length: use ch for readable text --- */ |
| 18 | .article-content { |
| 19 | max-width: 65ch; /* ~65 characters per line */ |
| 20 | } |
| 21 | |
| 22 | /* --- Layout: use viewport units --- */ |
| 23 | .hero { |
| 24 | width: 100vw; /* full viewport width */ |
| 25 | height: 100dvh; /* dynamic viewport height */ |
| 26 | } |
| 27 | |
| 28 | /* --- Responsive font with clamp --- */ |
| 29 | .responsive-text { |
| 30 | font-size: clamp(1rem, 2.5vw, 3rem); |
| 31 | /* minimum 1rem, preferred 2.5vw, maximum 3rem */ |
| 32 | } |
| 33 | |
| 34 | /* --- Avoid em for font-size in complex components --- */ |
| 35 | .component { |
| 36 | font-size: 0.875em; /* compounds with nesting! */ |
| 37 | /* Prefer rem to avoid compounding */ |
best practice
CSS offers multiple ways to specify colors, from simple named colors to advanced color spaces like OKLCH and color-mix().
| Method | Syntax | Example | Notes |
|---|---|---|---|
| Named | rebeccapurple | #639 | 148 named colors |
| Hex | #rgb / #rrggbb / #rrggbbaa | #00FF41 | 6-digit with alpha optional |
| rgb/rgba | rgb(r g b / a) | rgb(99 102 241 / 0.8) | Modern space-separated syntax |
| hsl/hsla | hsl(h s% l% / a) | hsl(120 100% 50%) | Intuitive hue wheel |
| oklch | oklch(l c h / a) | oklch(0.87 0.26 145) | Perceptually uniform, best for gradients |
| color-mix | color-mix(in srgb, a pct, b pct) | mix(#0f0, #66f) | Blend two colors |
| currentColor | currentColor | Inherits from color property | Great for SVG and borders |
| 1 | /* Named colors */ |
| 2 | .element { color: rebeccapurple; } |
| 3 | .error { color: red; } /* readable but imprecise */ |
| 4 | |
| 5 | /* Hexadecimal — most common */ |
| 6 | .primary { color: #00FF41; } /* green */ |
| 7 | .muted { color: #808080; } /* gray */ |
| 8 | .overlay { background: #0A0A0A; } /* dark bg */ |
| 9 | .translucent { background: #00FF4180; } /* 50% alpha */ |
| 10 | |
| 11 | /* Modern rgb() syntax — space separated, no commas */ |
| 12 | .card { |
| 13 | background: rgb(12 12 20 / 0.95); |
| 14 | border-color: rgb(30 30 48 / 1); |
| 15 | } |
| 16 | |
| 17 | /* HSL — intuitive color manipulation */ |
| 18 | .accent { |
| 19 | color: hsl(120 100% 50%); /* pure green */ |
| 20 | } |
| 21 | .dimmed { |
| 22 | color: hsl(120 100% 50% / 0.5); /* 50% transparent green */ |
| 23 | } |
| 24 | |
| 25 | /* OKLCH — perceptually uniform, best for design systems */ |
| 26 | :root { |
| 27 | --brand: oklch(0.87 0.26 145); |
| 28 | --brand-light: oklch(0.92 0.18 145); |
| 29 | --brand-dark: oklch(0.65 0.30 145); |
| 30 | } |
| 31 | |
| 32 | /* color-mix() — blend colors in real time */ |
| 33 | .btn-primary { |
| 34 | background: var(--brand); |
| 35 | } |
| 36 | .btn-primary:hover { |
| 37 | background: color-mix(in srgb, var(--brand), black 20%); |
| 38 | } |
| 39 | |
| 40 | /* currentColor — automatically matches text color */ |
| 41 | .icon { |
| 42 | fill: currentColor; /* inherits from text color */ |
| 43 | stroke: currentColor; |
| 44 | } |
| 45 | .button .icon { |
| 46 | color: white; /* icon inherits white */ |
| 47 | } |
pro tip
CSS provides powerful tools for decorating elements with backgrounds, gradients, borders, and shadows.
Background Shorthand
| 1 | /* Background shorthand — order: color image position/size repeat attachment origin clip */ |
| 2 | .hero { |
| 3 | background: #0A0A0A url("bg.jpg") center / cover no-repeat fixed; |
| 4 | } |
| 5 | |
| 6 | /* Equivalent longhand */ |
| 7 | .hero { |
| 8 | background-color: #0A0A0A; |
| 9 | background-image: url("bg.jpg"); |
| 10 | background-position: center; |
| 11 | background-size: cover; |
| 12 | background-repeat: no-repeat; |
| 13 | background-attachment: fixed; |
| 14 | } |
| 15 | |
| 16 | /* Multiple backgrounds (stacked front-to-back) */ |
| 17 | .card { |
| 18 | background: |
| 19 | linear-gradient(135deg, rgb(99 102 241 / 0.1), transparent), |
| 20 | radial-gradient(circle at top right, rgb(0 255 65 / 0.05), transparent), |
| 21 | #0A0A0A; |
| 22 | } |
| 23 | |
| 24 | /* Gradients */ |
| 25 | .linear-gradient { |
| 26 | background: linear-gradient(90deg, #00FF41, #00FF41, #EC4899); |
| 27 | } |
| 28 | |
| 29 | .radial-gradient { |
| 30 | background: radial-gradient(circle, #00FF41, #0A0A0A); |
| 31 | } |
| 32 | |
| 33 | .conic-gradient { |
| 34 | background: conic-gradient(from 0deg, #00FF41, #00FF41, #EC4899, #00FF41); |
| 35 | } |
Border & Border-Radius
| 1 | /* Border shorthand */ |
| 2 | .card { |
| 3 | border: 2px solid #222222; |
| 4 | } |
| 5 | |
| 6 | /* Border-radius — can be asymmetrical */ |
| 7 | .card { |
| 8 | border-radius: 8px; /* uniform */ |
| 9 | border-radius: 8px 16px; /* top-left+bot-right / top-right+bot-left */ |
| 10 | border-radius: 8px 4px 12px 6px; /* TL TR BR BL (clockwise) */ |
| 11 | border-radius: 50%; /* circle */ |
| 12 | } |
| 13 | |
| 14 | /* Border-image */ |
| 15 | .bordered { |
| 16 | border: 8px solid transparent; |
| 17 | border-image: linear-gradient(135deg, #00FF41, #00FF41) 1; |
| 18 | } |
| 19 | |
| 20 | /* Box-shadow syntax: x y blur spread color inset */ |
| 21 | .card-shadow { |
| 22 | box-shadow: |
| 23 | 0 1px 3px rgb(0 0 0 / 0.3), |
| 24 | 0 4px 12px rgb(0 0 0 / 0.15); /* layered shadows */ |
| 25 | } |
| 26 | |
| 27 | /* Inner shadow */ |
| 28 | .card-inset { |
| 29 | box-shadow: inset 0 2px 4px rgb(0 0 0 / 0.3); |
| 30 | } |
best practice
Custom properties (often called CSS variables) allow you to define reusable values. They cascade, can be overridden, and are accessible via JavaScript.
Defining and Using Custom Properties
| 1 | /* Define on :root for global scope */ |
| 2 | :root { |
| 3 | --brand: #00FF41; |
| 4 | --brand-dim: color-mix(in srgb, var(--brand), black 30%); |
| 5 | --surface: #0A0A0A; |
| 6 | --text: #E0E0E0; |
| 7 | --text-muted: #808080; |
| 8 | --border: #222222; |
| 9 | --radius-sm: 0.375rem; |
| 10 | --radius-md: 0.5rem; |
| 11 | --radius-lg: 0.75rem; |
| 12 | --space-xs: 0.25rem; |
| 13 | --space-sm: 0.5rem; |
| 14 | --space-md: 1rem; |
| 15 | --space-lg: 1.5rem; |
| 16 | --space-xl: 2rem; |
| 17 | } |
| 18 | |
| 19 | /* Use with var() */ |
| 20 | .card { |
| 21 | background: var(--surface); |
| 22 | color: var(--text); |
| 23 | border: 1px solid var(--border); |
| 24 | border-radius: var(--radius-md); |
| 25 | padding: var(--space-lg); |
| 26 | } |
| 27 | |
| 28 | /* Fallback value if property is not defined */ |
| 29 | .element { |
| 30 | color: var(--undefined-var, #00FF41); /* falls back to #00FF41 */ |
| 31 | background: var(--surface, #0A0A0A); /* uses definition or fallback */ |
| 32 | } |
| 33 | |
| 34 | /* Scoped overrides */ |
| 35 | .dark-card { |
| 36 | --surface: #05050A; |
| 37 | --border: #2A2A3E; |
| 38 | background: var(--surface); |
| 39 | } |
Typed Custom Properties with @property
The @property at-rule registers a custom property with a defined syntax, allowing the browser to type-check and — crucially — animate it:
| 1 | /* Register a typed custom property */ |
| 2 | @property --brand-color { |
| 3 | syntax: "<color>"; |
| 4 | inherits: false; |
| 5 | initial-value: #00FF41; |
| 6 | } |
| 7 | |
| 8 | @property --rotation { |
| 9 | syntax: "<angle>"; |
| 10 | inherits: false; |
| 11 | initial-value: 0deg; |
| 12 | } |
| 13 | |
| 14 | @property --spacing { |
| 15 | syntax: "<length>"; |
| 16 | inherits: true; |
| 17 | initial-value: 1rem; |
| 18 | } |
| 19 | |
| 20 | /* Now we can animate these properties! */ |
| 21 | @keyframes rotate-brand { |
| 22 | from { --rotation: 0deg; } |
| 23 | to { --rotation: 360deg; } |
| 24 | } |
| 25 | |
| 26 | .box { |
| 27 | --brand-color: #00FF41; |
| 28 | --rotation: 0deg; |
| 29 | background: var(--brand-color); |
| 30 | transform: rotate(var(--rotation)); |
| 31 | transition: --brand-color 0.3s ease; |
| 32 | } |
| 33 | |
| 34 | .box:hover { |
| 35 | --brand-color: #00FF41; |
| 36 | --rotation: 180deg; |
| 37 | } |
pro tip
Accessing Custom Properties from JavaScript
| 1 | // Read a custom property |
| 2 | const root = document.documentElement; |
| 3 | const brand = getComputedStyle(root) |
| 4 | .getPropertyValue("--brand") |
| 5 | .trim(); // "#00FF41" |
| 6 | |
| 7 | // Set a custom property |
| 8 | root.style.setProperty("--brand", "#00FF41"); |
| 9 | |
| 10 | // Use in dynamic theming |
| 11 | function setTheme(theme) { |
| 12 | const themes = { |
| 13 | dark: { |
| 14 | "--surface": "#0A0A0A", |
| 15 | "--text": "#E0E0E0", |
| 16 | }, |
| 17 | light: { |
| 18 | "--surface": "#FFFFFF", |
| 19 | "--text": "#1A1A1A", |
| 20 | }, |
| 21 | }; |
| 22 | const vars = themes[theme]; |
| 23 | Object.entries(vars).forEach(([key, val]) => { |
| 24 | root.style.setProperty(key, val); |
| 25 | }); |
| 26 | } |
Media queries allow you to apply CSS conditionally based on device characteristics like viewport size, resolution, color scheme, and user preferences.
Syntax & Logical Operators
| 1 | /* Basic syntax */ |
| 2 | @media (max-width: 768px) { |
| 3 | .sidebar { display: none; } |
| 4 | } |
| 5 | |
| 6 | /* Logical operators */ |
| 7 | @media (min-width: 640px) and (max-width: 1024px) { |
| 8 | /* AND: both conditions must match */ |
| 9 | .grid { grid-template-columns: 1fr 1fr; } |
| 10 | } |
| 11 | |
| 12 | @media (max-width: 480px), (orientation: landscape) { |
| 13 | /* OR: either condition matches */ |
| 14 | .nav { flex-direction: column; } |
| 15 | } |
| 16 | |
| 17 | @media not (hover: hover) { |
| 18 | /* NOT: negates the condition */ |
| 19 | .tooltip { display: none; } |
| 20 | } |
| 21 | |
| 22 | /* Chaining media features */ |
| 23 | @media (min-width: 768px) and (hover: hover) and (prefers-reduced-motion: no-preference) { |
| 24 | .card:hover { |
| 25 | transform: scale(1.02); |
| 26 | transition: transform 0.3s ease; |
| 27 | } |
| 28 | } |
Common Breakpoints
| Name | Min Width | Target Devices |
|---|---|---|
| sm | 640px | Large phones, small tablets |
| md | 768px | Tablets (portrait) |
| lg | 1024px | Tablets (landscape), small desktops |
| xl | 1280px | Desktop monitors |
| 2xl | 1536px | Large screens, ultrawide |
Preference Queries
| 1 | /* Dark/light mode */ |
| 2 | @media (prefers-color-scheme: dark) { |
| 3 | :root { |
| 4 | --surface: #0A0A0A; |
| 5 | --text: #E0E0E0; |
| 6 | } |
| 7 | } |
| 8 | |
| 9 | @media (prefers-color-scheme: light) { |
| 10 | :root { |
| 11 | --surface: #FFFFFF; |
| 12 | --text: #1A1A1A; |
| 13 | } |
| 14 | } |
| 15 | |
| 16 | /* Reduced motion */ |
| 17 | @media (prefers-reduced-motion: reduce) { |
| 18 | *, *::before, *::after { |
| 19 | animation-duration: 0.01ms !important; |
| 20 | animation-iteration-count: 1 !important; |
| 21 | transition-duration: 0.01ms !important; |
| 22 | } |
| 23 | } |
| 24 | |
| 25 | /* Reduced transparency */ |
| 26 | @media (prefers-reduced-transparency: reduce) { |
| 27 | .glass { |
| 28 | background: var(--surface); |
| 29 | backdrop-filter: none; |
| 30 | } |
| 31 | } |
| 32 | |
| 33 | /* High contrast */ |
| 34 | @media (prefers-contrast: high) { |
| 35 | .card { |
| 36 | border-width: 2px; |
| 37 | } |
| 38 | } |
| 39 | |
| 40 | /* Reduced data usage */ |
| 41 | @media (prefers-reduced-data: reduce) { |
| 42 | .hero { |
| 43 | background-image: none; |
| 44 | } |
| 45 | } |
warning
Container Queries
Container queries allow you to style elements based on their parent container's size, rather than the viewport. This is ideal for reusable components that need to adapt to various contexts.
| 1 | /* Define a containment context */ |
| 2 | .card-container { |
| 3 | container-type: inline-size; |
| 4 | container-name: card; |
| 5 | } |
| 6 | |
| 7 | /* Query based on container width */ |
| 8 | @container card (min-width: 400px) { |
| 9 | .card { |
| 10 | display: grid; |
| 11 | grid-template-columns: 200px 1fr; |
| 12 | } |
| 13 | } |
| 14 | |
| 15 | @container card (max-width: 399px) { |
| 16 | .card { |
| 17 | display: flex; |
| 18 | flex-direction: column; |
| 19 | } |
| 20 | } |
| 21 | |
| 22 | /* Style queries — check if container has a certain style */ |
| 23 | @container card style(--variant: featured) { |
| 24 | .card-title { font-size: 1.5rem; } |
| 25 | } |
Naming Conventions
Consistent naming conventions make CSS maintainable at scale. Two popular approaches are BEM and utility-first:
| 1 | /* BEM (Block Element Modifier) */ |
| 2 | .block {} /* component name */ |
| 3 | .block__element {} /* child of block */ |
| 4 | .block--modifier {} /* variant of block */ |
| 5 | |
| 6 | /* Example */ |
| 7 | .card {} |
| 8 | .card__title {} |
| 9 | .card__description {} |
| 10 | .card--featured {} |
| 11 | .card--compact {} |
| 12 | |
| 13 | /* Utility-first (e.g., Tailwind-style) */ |
| 14 | .flex { display: flex; } |
| 15 | .p-4 { padding: 1rem; } |
| 16 | .text-center { text-align: center; } |
| 17 | .bg-brand { background: var(--brand); } |
| 18 | |
| 19 | /* Hybrid approach — combine components + utilities */ |
| 20 | .card { |
| 21 | /* component styles */ |
| 22 | background: var(--surface); |
| 23 | border-radius: var(--radius-md); |
| 24 | } |
| 25 | .card.featured { |
| 26 | /* modifier with utility class */ |
| 27 | border-color: var(--brand); |
| 28 | } |
Performance Best Practices
Organization Strategy
| 1 | /* Suggested file structure for projects */ |
| 2 | styles/ |
| 3 | ├── reset.css /* CSS reset / normalize */ |
| 4 | ├── tokens.css /* Custom properties (colors, spacing, fonts) */ |
| 5 | ├── base.css /* Element selectors (body, headings, links) */ |
| 6 | ├── layout.css /* Grid, containers, page structure */ |
| 7 | ├── components/ /* Component-specific styles */ |
| 8 | │ ├── card.css |
| 9 | │ ├── button.css |
| 10 | │ └── nav.css |
| 11 | ├── utilities.css /* Utility classes */ |
| 12 | └── overrides.css /* Debug, print, fallback overrides */ |
| 13 | |
| 14 | /* Within a component file, organize by: */ |
| 15 | /* 1. Container / layout */ |
| 16 | /* 2. Children */ |
| 17 | /* 3. States (:hover, :active, .is-active) */ |
| 18 | /* 4. Variants (--featured, --compact) */ |
| 19 | /* 5. Media queries (component-scoped) */ |
best practice
Common Pitfalls to Avoid
✗ Overly Specific Selectors
div#main-content .container ul li a.link { color: blue; }This has specificity 0,1,2,4 — it cannot be overridden without !important.
✓ Flat, Low-Specificity Selectors
.link-primary { color: var(--brand); }A single class is easy to override and compose.
✗ Magic Numbers
margin-top: -3px; /* hack to align with sibling */Magic numbers break when content or fonts change. Use proper layout techniques instead.
✓ Intentional, Meaningful Values
display: flex; align-items: center;Flexbox and Grid provide robust, non-fragile alignment solutions.
The CSSOM is a map of all CSS selectors and their computed styles for each element in the DOM. The browser constructs the CSSOM alongside the DOM during the parsing phase. Understanding the CSSOM helps you write efficient selectors and diagnose rendering performance.
| 1 | // Access computed styles |
| 2 | const element = document.querySelector(".card"); |
| 3 | const styles = getComputedStyle(element); |
| 4 | |
| 5 | console.log(styles.color); // "rgb(224, 224, 224)" |
| 6 | console.log(styles.getPropertyValue("--brand")); // "#00FF41" |
| 7 | |
| 8 | // StyleSheet API |
| 9 | const sheet = document.styleSheets[0]; |
| 10 | const rules = sheet.cssRules; |
| 11 | |
| 12 | for (const rule of rules) { |
| 13 | if (rule instanceof CSSStyleRule) { |
| 14 | console.log(rule.selectorText); // ".card" |
| 15 | console.log(rule.style.color); // "#E0E0E0" |
| 16 | } |
| 17 | } |
info