Create resilient interfaces by understanding the cascade, box model, sizing, typography, modern layout, responsiveness, and accessibility-friendly visual states.
Reason about the cascade and specificity before adding overrides
Control sizing and spacing with the box model
Choose Flexbox or Grid based on the layout relationship
Create responsive components with modern CSS primitives
Build accessible interaction, motion, and theme states
The cascade resolves competing declarations using origin, importance, cascade layers, specificity, scoping proximity, and source order.
More specificity is rarely the best first fix. Keep selectors shallow and use semantic custom properties so component styles remain predictable.
Prefer classes with low, consistent specificity
Inspect the winning declaration before adding another override
Use cascade layers to separate resets, components, and utilities
@layer reset, base, components, utilities;
@layer base {
:root {
--surface: #111827;
--text: #f9fafb;
--accent: #60a5fa;
}
body {
margin: 0;
color: var(--text);
background: var(--surface);
}
}
@layer components {
.button {
border: 0;
border-radius: 0.75rem;
padding: 0.7rem 1rem;
background: var(--accent);
}
}Every element is laid out as a box made from content, padding, border, and margin. Width and height normally apply to the content box unless box-sizing changes that behavior.
Using border-box globally makes component sizing easier because declared dimensions include padding and borders.
Understand content, padding, border, and margin
Use box-sizing: border-box for predictable sizing
Use margin for outside separation and padding for inside breathing room
*,
*::before,
*::after {
box-sizing: border-box;
}
.panel {
width: min(100%, 42rem);
padding: 1.25rem;
border: 1px solid #334155;
border-radius: 1rem;
margin-inline: auto;
}The display property controls how an element participates in layout. Common values include block, inline, inline-block, flex, grid, and none.
Choosing the right display mode lets the browser handle relationships naturally instead of relying on positional hacks.
Use block flow for ordinary document structure
Use inline for text-level content
Use flex or grid when children need coordinated layout
.toolbar {
display: flex;
align-items: center;
gap: 0.75rem;
}
.toolbar__title {
margin-right: auto;
}
.statusBadge {
display: inline-flex;
align-items: center;
gap: 0.35rem;
padding: 0.25rem 0.55rem;
border-radius: 999px;
}CSS supports absolute and relative units. rem relates to the root font size, em relates to the current element, percentages relate to a containing dimension, and viewport units relate to the viewport.
Modern sizing functions such as min(), max(), clamp(), and minmax() make layouts fluid without many breakpoints.
Prefer rem for scalable spacing and type
Use percentages and fr units for flexible layout
Use clamp for bounded fluid sizing
.pageShell {
width: min(100% - 2rem, 72rem);
margin-inline: auto;
}
.heroTitle {
font-size: clamp(2rem, 5vw, 4.5rem);
line-height: 1.05;
letter-spacing: -0.04em;
}
.heroCopy {
max-width: 65ch;
}Flexbox lays items out along one primary axis and provides tools for alignment, distribution, wrapping, and flexible growth.
It works especially well for navbars, toolbars, rows of controls, and components where items mainly relate in one direction.
Main-axis behavior is controlled by justify-content
Cross-axis behavior is controlled by align-items
flex: 1 allows an item to grow and shrink into available space
.toolbar {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.75rem;
padding: 1rem;
}
.toolbar__search {
flex: 1 1 18rem;
min-width: 0;
}
.toolbar__actions {
display: flex;
gap: 0.5rem;
}Grid coordinates rows and columns together, making it well suited for page shells, dashboards, card collections, and two-dimensional relationships.
Functions such as repeat(), auto-fit, minmax(), and named areas make grids adaptable without manually enumerating every track.
Use Grid for two-dimensional layout relationships
Use minmax(0, 1fr) when tracks contain long text or code
Use auto-fit or auto-fill for adaptive repeated columns
.cardGrid {
display: grid;
grid-template-columns:
repeat(
auto-fit,
minmax(min(18rem, 100%), 1fr)
);
gap: 1rem;
}
.card {
min-width: 0;
padding: 1rem;
border: 1px solid #334155;
border-radius: 1rem;
}Positioning removes or offsets elements relative to normal flow. Relative positioning establishes an anchor, absolute positions against a containing block, fixed attaches to the viewport, and sticky responds to scroll position.
Use positioning for overlays and anchored UI, not as a replacement for ordinary document layout.
Keep major page layout in normal flow
Use absolute positioning for anchored decorative or overlay elements
Use sticky for headers and sidebars that should remain visible while scrolling
.profileCard {
position: relative;
padding: 1.25rem;
border-radius: 1rem;
}
.profileCard__badge {
position: absolute;
top: 0.75rem;
right: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 999px;
background: #22c55e;
color: #052e16;
}Readable typography depends on font size, line height, line length, weight, contrast, and hierarchy rather than only the font family.
Relative units and restrained weight choices help interfaces remain readable across zoom levels and devices.
Keep body line length near a readable measure
Use line-height appropriate to text density
Do not rely on weight or color alone to convey hierarchy
:root {
--font-sans:
Inter,
ui-sans-serif,
system-ui,
sans-serif;
}
body {
font-family: var(--font-sans);
font-size: 1rem;
line-height: 1.6;
}
h1 {
font-size: clamp(2rem, 4vw, 3.5rem);
line-height: 1.1;
letter-spacing: -0.035em;
}
.prose {
max-width: 68ch;
}CSS custom properties store reusable values that participate in the cascade. Semantic tokens describe purpose, making themes easier to maintain than scattering literal colors across components.
Theme attributes or classes can override token values while components continue consuming the same semantic variables.
Name tokens by purpose instead of literal color
Keep component styles dependent on semantic variables
Switch themes by changing token values at a higher scope
:root {
--surface-primary: #ffffff;
--surface-elevated: #f8fafc;
--text-primary: #0f172a;
--text-muted: #64748b;
--border-subtle: #e2e8f0;
--accent-primary: #2563eb;
}
[data-theme="dark"] {
--surface-primary: #0b1220;
--surface-elevated: #111827;
--text-primary: #f8fafc;
--text-muted: #94a3b8;
--border-subtle: #334155;
--accent-primary: #60a5fa;
}
.panel {
color: var(--text-primary);
background: var(--surface-elevated);
border: 1px solid var(--border-subtle);
}Start with a layout that works in narrow space, then add complexity only when the content needs it. Fluid sizing and wrapping often remove the need for device-specific breakpoints.
A component should usually respond to the space it receives rather than assuming a particular device.
Let content determine useful breakpoints
Test zoom and long text as well as common device widths
Prefer flexible layouts before adding media queries
.page {
display: grid;
gap: 1rem;
}
.sidebar {
order: 2;
}
@media (min-width: 48rem) {
.page {
grid-template-columns: 16rem minmax(0, 1fr);
align-items: start;
}
.sidebar {
order: initial;
position: sticky;
top: 1rem;
}
}Container queries let a component respond to the size of its containing element instead of the entire viewport.
They are useful for reusable components that can appear in a sidebar, dashboard column, modal, or full-width page.
Declare a query container with container-type
Style components based on their available space
Use them when viewport breakpoints do not describe the component relationship
.cardRegion {
container-type: inline-size;
}
.profileCard {
display: grid;
gap: 1rem;
}
@container (min-width: 32rem) {
.profileCard {
grid-template-columns: auto minmax(0, 1fr);
align-items: center;
}
.profileCard__avatar {
width: 6rem;
height: 6rem;
}
}Interactive components need visible hover, active, disabled, and focus states. Keyboard users must be able to see where focus currently is.
Motion should support understanding without becoming a barrier. Respect the user's reduced-motion preference when animations are nonessential.
Use :focus-visible for keyboard focus treatment
Do not remove focus outlines without replacing them
Respect prefers-reduced-motion
.button {
transition:
transform 160ms ease,
background-color 160ms ease;
}
.button:hover {
transform: translateY(-1px);
}
.button:active {
transform: translateY(0);
}
.button:focus-visible {
outline: 3px solid #93c5fd;
outline-offset: 3px;
}
.button:disabled {
cursor: not-allowed;
opacity: 0.55;
}
@media (prefers-reduced-motion: reduce) {
.button {
transition: none;
}
}