ferred size */
font-size: 100%;
/* Layout tokens using rem for global consistency */
--space-xs: 0.25rem;
--space-sm: 0.5rem;
--space-md: 1rem;
--space-lg: 2rem;
--space-xl: 4rem;
/* Typography tokens */
--font-body: 1rem;
--font-heading: 2rem;
--line-height: 1.5;
}
body {
font-family: system-ui, -apple-system, sans-serif;
font-size: var(--font-body);
line-height: var(--line-height);
margin: 0;
}
#### Step 2: Implement Context-Aware Components
Use `em` units within components to allow them to scale relative to their own context. This is essential for icons and internal spacing that should grow or shrink with the component's font size.
```css
/* components/Card.css */
.card {
/* Layout uses rem */
padding: var(--space-md);
margin-bottom: var(--space-lg);
border-radius: 0.5rem;
background-color: #ffffff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
/* Component font size sets the context for em units */
font-size: var(--font-body);
}
.card__header {
font-size: var(--font-heading);
margin-bottom: var(--space-sm);
}
.card__icon {
/* Scales relative to .card font-size, not root */
font-size: 1.5em;
vertical-align: middle;
}
.card__actions {
/* Spacing scales with card text size */
gap: 0.5em;
display: flex;
margin-top: var(--space-md);
}
Step 3: Responsive Architecture with Logical Breakpoints
Media queries should be used to alter layout structure, not just tweak values. Adopt a mobile-first approach and use logical properties (inset-block, inset-inline) for better internationalization support.
/* layout/Grid.css */
.grid-container {
display: grid;
gap: var(--space-md);
/* Mobile: Single column */
grid-template-columns: 1fr;
}
/* Tablet and up */
@media (min-width: 768px) {
.grid-container {
grid-template-columns: repeat(2, 1fr);
gap: var(--space-lg);
}
}
/* Desktop and up */
@media (min-width: 1024px) {
.grid-container {
grid-template-columns: repeat(3, 1fr);
max-width: 1200px;
margin-inline: auto;
}
}
Step 4: Interaction Patterns with Performance in Mind
Transitions should use hardware-accelerated properties like transform and opacity. Avoid animating width, height, or top/left as they trigger layout recalculations. Use custom cubic-bezier curves for natural motion.
/* components/Button.css */
.action-btn {
display: inline-flex;
align-items: center;
padding: var(--space-sm) var(--space-md);
background-color: #0056b3;
color: #ffffff;
border: none;
border-radius: 0.25rem;
cursor: pointer;
font-size: var(--font-body);
/* Smooth transition with custom easing */
transition:
transform 200ms cubic-bezier(0.4, 0, 0.2, 1),
background-color 200ms ease;
}
.action-btn:hover {
transform: translateY(-2px);
background-color: #004494;
}
.action-btn:active {
transform: translateY(0);
}
/* Accessibility: Respect reduced motion preferences */
@media (prefers-reduced-motion: reduce) {
.action-btn {
transition: none;
}
}
Step 5: Positioning Strategy for Overlays and Anchors
Positioning requires a clear hierarchy. Always establish a positioning context on the parent before using absolute positioning on children. Use position: sticky for navigation elements that need to persist during scroll without detaching from the document flow.
/* components/Notification.css */
.notification-wrapper {
/* Establishes positioning context for absolute children */
position: relative;
display: inline-block;
}
.notification-badge {
/* Positioned relative to .notification-wrapper */
position: absolute;
inset-block-start: -0.25rem;
inset-inline-end: -0.25rem;
width: 1.25rem;
height: 1.25rem;
background-color: #dc3545;
color: #ffffff;
border-radius: 50%;
font-size: 0.75rem;
display: flex;
align-items: center;
justify-content: center;
}
/* Sticky header pattern */
.site-header {
position: sticky;
inset-block-start: 0;
z-index: 100;
background-color: #ffffff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
Pitfall Guide
-
The em Compounding Trap
- Explanation: When
em units are used for font size in nested elements, the sizes multiply. A child with font-size: 1.2em inside a parent with font-size: 1.2em results in 1.44em relative to the grandparent. This causes exponential size drift in deep component trees.
- Fix: Reserve
em for spacing and sizing within a component where the parent font size is the intended reference. Use rem for font sizes in nested structures to maintain consistency.
-
Hardcoded Root Font Size
- Explanation: Setting
html { font-size: 16px; } overrides the user's browser preferences. Users who set their base font size to 20px for readability will see your interface render at 16px, breaking accessibility.
- Fix: Always use
html { font-size: 100%; }. This ensures 1rem equals the user's preferred size.
-
Orphaned Absolute Elements
- Explanation: An element with
position: absolute searches up the DOM tree for the nearest positioned ancestor. If no ancestor has position set to relative, absolute, or fixed, the element positions relative to the initial containing block (usually the viewport), causing it to detach from its intended container.
- Fix: Always verify that the parent container has
position: relative when using absolute positioning for children. Use linting rules to catch missing positioning contexts.
-
Animating Layout Properties
- Explanation: Transitioning properties like
width, height, top, or left forces the browser to recalculate layout on every frame, leading to janky animations and high CPU usage.
- Fix: Animate
transform and opacity whenever possible. These properties are handled by the compositor thread and do not trigger layout recalculations.
-
Ignoring prefers-reduced-motion
- Explanation: Transitions and animations can trigger vestibular disorders in users with motion sensitivity. Ignoring this preference is an accessibility violation and can cause physical discomfort.
- Fix: Always include a media query for
prefers-reduced-motion: reduce to disable or simplify animations. Provide static alternatives for critical interactions.
-
Z-Index Stacking Context Confusion
- Explanation:
position: fixed and position: absolute elements create new stacking contexts. A z-index value may not behave as expected if the element is nested inside a container with a lower stacking context.
- Fix: Manage
z-index values using a defined scale (e.g., --z-dropdown: 100, --z-modal: 200). Avoid arbitrary high values like 9999. Ensure parent containers do not inadvertently clip children with overflow: hidden when using fixed positioning.
-
Media Query Overload
- Explanation: Creating breakpoints for every possible device width leads to maintenance hell and bloated CSS. It also fails to account for container-specific responsiveness.
- Fix: Limit breakpoints to logical layout shifts (e.g., column changes). Consider using Container Queries for component-level responsiveness, allowing components to adapt based on their container width rather than the viewport.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Global Spacing System | rem units | Ensures consistent scaling across the entire application based on root settings. | Low |
| Component Icon Sizing | em units | Icons scale proportionally with the component's font size, maintaining visual hierarchy. | Low |
| Sticky Navigation | position: sticky | Native browser support provides smooth scrolling behavior without JavaScript overhead. | None |
| Modal Overlay | position: fixed | Detaches from document flow and stays anchored to the viewport during scroll. | None |
| Hover Effects | transform + opacity | Hardware-accelerated properties ensure 60fps animations without layout thrashing. | Low |
| Responsive Grid | Media Queries + Grid | Declarative layout changes at logical breakpoints reduce CSS complexity. | Low |
Configuration Template
/* tokens.css */
:root {
/* Base Scale */
--base-font-size: 100%;
/* Spacing Scale (rem) */
--space-unit: 0.25rem;
--space-1: calc(var(--space-unit) * 1);
--space-2: calc(var(--space-unit) * 2);
--space-3: calc(var(--space-unit) * 4);
--space-4: calc(var(--space-unit) * 6);
--space-5: calc(var(--space-unit) * 8);
/* Typography Scale */
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.25rem;
--font-size-xl: 1.5rem;
--font-size-2xl: 2rem;
/* Z-Index Scale */
--z-base: 1;
--z-dropdown: 10;
--z-sticky: 20;
--z-overlay: 30;
--z-modal: 40;
--z-toast: 50;
/* Transition Config */
--transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
--transition-normal: 250ms cubic-bezier(0.4, 0, 0.2, 1);
}
/* Reset */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
font-size: var(--base-font-size);
}
body {
font-family: system-ui, -apple-system, sans-serif;
font-size: var(--font-size-base);
line-height: 1.5;
color: #1a1a1a;
background-color: #f5f5f5;
}
/* Reduced Motion */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
Quick Start Guide
- Initialize Tokens: Copy the
tokens.css configuration into your project. This establishes the scaling foundation, z-index hierarchy, and accessibility defaults.
- Build Components with
rem: Create your UI components using rem for all layout properties. Use CSS variables from the token set for spacing and typography.
- Add Context Scaling: For components that need internal scaling (like buttons with icons), apply
em units to child elements. Ensure the parent component defines a base font-size.
- Implement Responsiveness: Wrap layout containers in media queries starting from mobile. Use
min-width breakpoints to expand grids and adjust spacing at 768px and 1024px.
- Polish Interactions: Add hover and focus states using
transform and opacity. Apply var(--transition-normal) for consistent motion. Verify that prefers-reduced-motion disables these effects automatically.