697 lines
20 KiB
Markdown
697 lines
20 KiB
Markdown
# Production-Ready SCSS Architecture & Responsive Design for 2025
|
||
|
||
Modern CSS has fundamentally changed since 2023. **Container queries are now production-ready** with 90%+ browser support, making component-level responsiveness a reality. OKLCH colors enable perceptually uniform palettes, `@layer` provides cascade control, and `text-wrap: balance` eliminates awkward heading breaks. Meanwhile, SCSS's `@import` is officially deprecated—`@use` and `@forward` are mandatory. This report provides senior-level patterns for building a bulletproof design system that works flawlessly from 300px to 1800px+.
|
||
|
||
## Modern CSS reset for 2025 browsers
|
||
|
||
The reset landscape has shifted from "erase everything" to "minimal opinionated baseline." Browser consistency is now excellent, so resets focus on improving authoring experience and accessibility rather than fixing bugs.
|
||
|
||
**Essential reset components for production:**
|
||
|
||
```scss
|
||
// _reset.scss - Modern 2025 Reset
|
||
|
||
*, *::before, *::after {
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
* {
|
||
margin: 0;
|
||
}
|
||
|
||
html {
|
||
-moz-text-size-adjust: none;
|
||
-webkit-text-size-adjust: none;
|
||
text-size-adjust: none;
|
||
}
|
||
|
||
// Enable animations to auto/fit-content (Chrome/Edge 2025)
|
||
@media (prefers-reduced-motion: no-preference) {
|
||
html {
|
||
interpolate-size: allow-keywords;
|
||
scroll-behavior: smooth;
|
||
}
|
||
}
|
||
|
||
body {
|
||
min-height: 100vh;
|
||
line-height: 1.5;
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
|
||
h1, h2, h3, h4, h5, h6 {
|
||
line-height: 1.1;
|
||
text-wrap: balance;
|
||
overflow-wrap: break-word;
|
||
}
|
||
|
||
p {
|
||
text-wrap: pretty;
|
||
overflow-wrap: break-word;
|
||
}
|
||
|
||
img, picture, video, canvas, svg {
|
||
display: block;
|
||
max-width: 100%;
|
||
}
|
||
|
||
input, button, textarea, select {
|
||
font: inherit;
|
||
}
|
||
|
||
textarea:not([rows]) {
|
||
min-height: 10em;
|
||
}
|
||
|
||
// Safari VoiceOver list semantics fix
|
||
ul[role='list'], ol[role='list'] {
|
||
list-style: none;
|
||
}
|
||
|
||
:target {
|
||
scroll-margin-block: 5ex;
|
||
}
|
||
|
||
// SPA root stacking context
|
||
#root, #__next {
|
||
isolation: isolate;
|
||
}
|
||
|
||
// Safe area for notched devices
|
||
@supports(padding: max(0px)) {
|
||
body {
|
||
padding-left: max(1rem, env(safe-area-inset-left));
|
||
padding-right: max(1rem, env(safe-area-inset-right));
|
||
}
|
||
}
|
||
|
||
// Dark mode initialization
|
||
html {
|
||
color-scheme: dark light;
|
||
}
|
||
```
|
||
|
||
### What to add vs remove in 2025
|
||
|
||
The **new additions** that matter: `interpolate-size: allow-keywords` enables smooth animations to `auto` and `fit-content`; `text-wrap: balance` creates visually balanced headings (max **6 lines** in Chrome); `text-wrap: pretty` prevents orphans in paragraphs. Safe area insets using `env()` with `max()` fallback handle iPhone notches properly.
|
||
|
||
**Remove these obsolete hacks:** All `-ms-` prefixes (IE11 is completely dead since June 2022), the old `-webkit-appearance: button` form control fixes, monospace font-family double declaration hack, `constant()` for notch (replaced by `env()` since iOS 11.2), and any `height: 100%` on html/body—dynamic viewport units (`dvh`, `svh`) are well-supported now.
|
||
|
||
### Controversial decisions resolved
|
||
|
||
**Box-sizing globally**: Still best practice. Performance impact is negligible—Paul Irish confirmed it's "as fast as h1 as a selector." Modern browsers optimize `*` selectors efficiently. The Microsoft Edge team notes "there are more strategic parts to optimize."
|
||
|
||
**Scroll-behavior smooth**: Only with motion preference check. Apply inside `@media (prefers-reduced-motion: no-preference)` to avoid triggering vestibular disorders. Never apply globally without this check.
|
||
|
||
**Line-height 1.5**: Apply to body text, but reduce headings to **1.1**. WCAG requires ≥1.5 for body text accessibility, but headings look better with tighter leading.
|
||
|
||
## Comprehensive design token system
|
||
|
||
### Spacing scale: 8px base with rem output
|
||
|
||
The **8px base unit** dominates modern design systems (Atlassian, Material Design, IBM Carbon). Use 4px half-steps for fine control in dense interfaces.
|
||
|
||
```scss
|
||
// _tokens.scss
|
||
|
||
// Spacing (8px base, rem output)
|
||
$space-1: 0.25rem; // 4px
|
||
$space-2: 0.5rem; // 8px
|
||
$space-3: 0.75rem; // 12px
|
||
$space-4: 1rem; // 16px
|
||
$space-5: 1.25rem; // 20px
|
||
$space-6: 1.5rem; // 24px
|
||
$space-8: 2rem; // 32px
|
||
$space-10: 2.5rem; // 40px
|
||
$space-12: 3rem; // 48px
|
||
$space-16: 4rem; // 64px
|
||
|
||
// CSS custom properties for runtime flexibility
|
||
:root {
|
||
--space-1: #{$space-1};
|
||
--space-2: #{$space-2};
|
||
--space-3: #{$space-3};
|
||
--space-4: #{$space-4};
|
||
--space-6: #{$space-6};
|
||
--space-8: #{$space-8};
|
||
--space-12: #{$space-12};
|
||
--space-16: #{$space-16};
|
||
}
|
||
```
|
||
|
||
Use **numeric naming** (`space-1, space-2, space-4`) rather than t-shirt sizes—it maps directly to multipliers and scales infinitely. Always use `rem` for spacing to respect user font-size preferences (accessibility requirement).
|
||
|
||
### Typography scale: Major Third ratio
|
||
|
||
The **1.25 (Major Third) ratio** is the most versatile default. It provides clear hierarchy without excessive jumps between sizes.
|
||
|
||
```scss
|
||
// Typography scale (1.25 ratio)
|
||
:root {
|
||
--font-size-xs: 0.64rem; // ~10px
|
||
--font-size-sm: 0.8rem; // ~13px
|
||
--font-size-base: 1rem; // 16px
|
||
--font-size-md: 1.25rem; // 20px
|
||
--font-size-lg: 1.563rem; // 25px
|
||
--font-size-xl: 1.953rem; // 31px
|
||
--font-size-2xl: 2.441rem; // 39px
|
||
--font-size-3xl: 3.052rem; // 49px
|
||
|
||
// Font weights
|
||
--font-weight-regular: 400;
|
||
--font-weight-medium: 500;
|
||
--font-weight-semibold: 600;
|
||
--font-weight-bold: 700;
|
||
|
||
// Line heights (paired with sizes)
|
||
--line-height-tight: 1.1;
|
||
--line-height-snug: 1.25;
|
||
--line-height-normal: 1.5;
|
||
--line-height-relaxed: 1.625;
|
||
|
||
// Tracking
|
||
--tracking-tight: -0.025em;
|
||
--tracking-normal: 0;
|
||
--tracking-wide: 0.025em;
|
||
}
|
||
```
|
||
|
||
### Color system: OKLCH is production-ready
|
||
|
||
**OKLCH is the 2025 standard** with support in Chrome 111+, Safari 15.4+, Firefox 113+. It provides perceptually uniform lightness, better color manipulation (no muddy gradients), wide-gamut P3 support, and native `color-mix()` compatibility.
|
||
|
||
```scss
|
||
// Color tokens using OKLCH
|
||
:root {
|
||
// Primitives
|
||
--color-blue-500: oklch(60% 0.2 250);
|
||
--color-blue-600: oklch(50% 0.2 250);
|
||
--color-gray-100: oklch(95% 0.01 250);
|
||
--color-gray-900: oklch(20% 0.01 250);
|
||
|
||
// Semantic tokens (light mode)
|
||
--color-primary: var(--color-blue-500);
|
||
--color-primary-hover: var(--color-blue-600);
|
||
--color-text: var(--color-gray-900);
|
||
--color-text-muted: oklch(45% 0 0);
|
||
--color-bg: oklch(98% 0 0);
|
||
--color-surface: oklch(100% 0 0);
|
||
|
||
// State variants using relative color syntax
|
||
--color-primary-disabled: oklch(from var(--color-primary) l calc(c * 0.3) h / 0.5);
|
||
}
|
||
|
||
// Dark mode overrides
|
||
[data-theme="dark"] {
|
||
--color-text: oklch(95% 0 0);
|
||
--color-text-muted: oklch(70% 0 0);
|
||
--color-bg: oklch(15% 0 0);
|
||
--color-surface: oklch(22% 0 0);
|
||
--color-primary: oklch(65% 0.18 250);
|
||
}
|
||
```
|
||
|
||
### CSS custom properties vs SCSS variables
|
||
|
||
**Use both strategically:** SCSS variables for build-time values (breakpoints, calculations, media queries), CSS custom properties for runtime theming. SCSS variables can't be used in media queries—`@media (min-width: $breakpoint-md)` works, but `@media (min-width: var(--breakpoint-md))` does not.
|
||
|
||
Performance difference is **~0.8% slower** with CSS custom properties—negligible for most applications. The real performance concern is recalculating descendants when changing variables at a parent level.
|
||
|
||
### Other essential tokens
|
||
|
||
```scss
|
||
:root {
|
||
// Border radius
|
||
--radius-sm: 0.125rem; // 2px
|
||
--radius-md: 0.25rem; // 4px
|
||
--radius-lg: 0.5rem; // 8px
|
||
--radius-xl: 1rem; // 16px
|
||
--radius-full: 9999px; // pill
|
||
|
||
// Shadows (4-6 levels)
|
||
--shadow-sm: 0 1px 2px 0 oklch(0% 0 0 / 0.05);
|
||
--shadow-md: 0 4px 6px -1px oklch(0% 0 0 / 0.1);
|
||
--shadow-lg: 0 10px 15px -3px oklch(0% 0 0 / 0.1);
|
||
--shadow-xl: 0 20px 25px -5px oklch(0% 0 0 / 0.1);
|
||
|
||
// Z-index scale
|
||
--z-dropdown: 100;
|
||
--z-sticky: 200;
|
||
--z-fixed: 300;
|
||
--z-modal: 500;
|
||
--z-tooltip: 800;
|
||
|
||
// Motion
|
||
--duration-fast: 100ms;
|
||
--duration-normal: 200ms;
|
||
--duration-slow: 300ms;
|
||
--ease-default: cubic-bezier(0.4, 0, 0.2, 1);
|
||
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
|
||
}
|
||
|
||
// Breakpoints (SCSS only—can't use CSS vars in media queries)
|
||
$breakpoint-sm: 640px;
|
||
$breakpoint-md: 768px;
|
||
$breakpoint-lg: 1024px;
|
||
$breakpoint-xl: 1280px;
|
||
$breakpoint-2xl: 1536px;
|
||
```
|
||
|
||
## Essential production mixins
|
||
|
||
### Responsive breakpoint mixin
|
||
|
||
```scss
|
||
// _mixins.scss
|
||
|
||
$breakpoints: (
|
||
'sm': 640px,
|
||
'md': 768px,
|
||
'lg': 1024px,
|
||
'xl': 1280px,
|
||
'2xl': 1536px
|
||
);
|
||
|
||
@mixin breakpoint($size) {
|
||
@media (min-width: map-get($breakpoints, $size)) {
|
||
@content;
|
||
}
|
||
}
|
||
|
||
// Usage
|
||
.card {
|
||
padding: 1rem;
|
||
@include breakpoint('md') {
|
||
padding: 2rem;
|
||
}
|
||
}
|
||
```
|
||
|
||
### Fluid typography mixin
|
||
|
||
```scss
|
||
@function fluid-type($min-size, $max-size, $min-vw: 320px, $max-vw: 1200px) {
|
||
$slope: math.div($max-size - $min-size, $max-vw - $min-vw);
|
||
$intercept: $min-size - ($slope * $min-vw);
|
||
@return clamp(#{$min-size}, #{$intercept} + #{$slope * 100}vw, #{$max-size});
|
||
}
|
||
|
||
// Usage
|
||
h1 {
|
||
font-size: fluid-type(1.75rem, 3rem);
|
||
}
|
||
```
|
||
|
||
### Container query mixin
|
||
|
||
```scss
|
||
@mixin container-query($name, $min-width) {
|
||
@container #{$name} (min-width: #{$min-width}) {
|
||
@content;
|
||
}
|
||
}
|
||
|
||
// Usage
|
||
.card-container {
|
||
container: card / inline-size;
|
||
}
|
||
|
||
.card-content {
|
||
@include container-query(card, 400px) {
|
||
flex-direction: row;
|
||
}
|
||
}
|
||
```
|
||
|
||
### Accessibility mixins
|
||
|
||
```scss
|
||
// Screen reader only
|
||
@mixin sr-only {
|
||
clip: rect(0 0 0 0);
|
||
clip-path: inset(50%);
|
||
height: 1px;
|
||
width: 1px;
|
||
margin: -1px;
|
||
overflow: hidden;
|
||
padding: 0;
|
||
position: absolute;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
// Modern focus state (WCAG compliant)
|
||
@mixin focus-visible-ring($color: currentColor, $offset: 2px) {
|
||
&:focus-visible {
|
||
outline: 2px solid $color;
|
||
outline-offset: $offset;
|
||
}
|
||
&:focus:not(:focus-visible) {
|
||
outline: none;
|
||
}
|
||
}
|
||
|
||
// Button reset
|
||
@mixin reset-button {
|
||
appearance: none;
|
||
background: none;
|
||
border: none;
|
||
padding: 0;
|
||
font: inherit;
|
||
color: inherit;
|
||
cursor: pointer;
|
||
}
|
||
|
||
// Truncation
|
||
@mixin truncate-single {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
@mixin truncate-multiline($lines: 3) {
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: $lines;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
}
|
||
```
|
||
|
||
### What NOT to include in mixins
|
||
|
||
**Vendor prefix mixins are obsolete.** Autoprefixer handles this at build time—manual prefix mixins waste code. Configure `browserslist` and let PostCSS do the work:
|
||
|
||
```json
|
||
{
|
||
"browserslist": [
|
||
"> 0.5%",
|
||
"last 2 versions",
|
||
"not dead",
|
||
"not op_mini all"
|
||
]
|
||
}
|
||
```
|
||
|
||
Remove any clearfix hacks (use `display: flow-root` or Grid/Flexbox), aspect ratio mixins (native `aspect-ratio` works everywhere), and hardware acceleration mixins (`will-change` should be applied sparingly and dynamically, not via blanket mixins).
|
||
|
||
## Flawless responsive design at every pixel width
|
||
|
||
### Container queries are production-ready
|
||
|
||
With **90%+ browser support** (Chrome 105+, Firefox 110+, Safari 16+), container queries are ready for production. They enable true component-level responsiveness—components adapt to their container rather than the viewport.
|
||
|
||
```scss
|
||
// Container query pattern
|
||
.card-wrapper {
|
||
container-type: inline-size;
|
||
container-name: card;
|
||
}
|
||
|
||
.card {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 1rem;
|
||
}
|
||
|
||
@container card (min-width: 400px) {
|
||
.card {
|
||
flex-direction: row;
|
||
}
|
||
}
|
||
```
|
||
|
||
**When to use each approach:**
|
||
|
||
- **Container queries:** Reusable components (cards, widgets), elements appearing in different contexts (main content vs sidebar)
|
||
- **Media queries:** Global page layout, navigation changes, user preference queries (`prefers-reduced-motion`)
|
||
|
||
### Fluid typography that scales flawlessly
|
||
|
||
Every font-size should be fluid, not just headings. Use **clamp()** with rem + vw for accessibility compliance—pure `vw` units don't respond to browser zoom (WCAG violation).
|
||
|
||
```scss
|
||
:root {
|
||
// Fluid scale from Utopia.fyi
|
||
--step-0: clamp(1.13rem, 1.08rem + 0.22vw, 1.25rem);
|
||
--step-1: clamp(1.35rem, 1.24rem + 0.55vw, 1.67rem);
|
||
--step-2: clamp(1.62rem, 1.41rem + 1.05vw, 2.22rem);
|
||
--step-3: clamp(1.94rem, 1.59rem + 1.77vw, 2.96rem);
|
||
--step-4: clamp(2.33rem, 1.77rem + 2.81vw, 3.95rem);
|
||
|
||
// Fluid spacing
|
||
--space-s: clamp(1rem, 0.92rem + 0.39vw, 1.25rem);
|
||
--space-m: clamp(1.5rem, 1.38rem + 0.58vw, 1.875rem);
|
||
--space-l: clamp(2rem, 1.85rem + 0.77vw, 2.5rem);
|
||
}
|
||
```
|
||
|
||
### Layouts that never break
|
||
|
||
The key to avoiding awkward in-between layouts is using **intrinsic sizing patterns** that work at any width:
|
||
|
||
```scss
|
||
// Holy grail grid pattern
|
||
.grid-layout {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
|
||
gap: 1rem;
|
||
}
|
||
|
||
// Sidebar pattern that never breaks
|
||
.with-sidebar {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.with-sidebar > :first-child {
|
||
flex-basis: 20rem;
|
||
flex-grow: 1;
|
||
}
|
||
|
||
.with-sidebar > :last-child {
|
||
flex-basis: 0;
|
||
flex-grow: 999;
|
||
min-inline-size: 50%;
|
||
}
|
||
```
|
||
|
||
The `min(100%, 250px)` trick prevents overflow on small viewports. Using `auto-fit` with `minmax()` lets the grid automatically adjust column count based on available space—no media queries needed.
|
||
|
||
### Solving the in-between problem
|
||
|
||
**More fluid, fewer breakpoints** is the 2025 approach. Use `clamp()` for sizing, let intrinsic sizing work, and reserve breakpoints for major layout shifts. **3-5 breakpoints are optimal** for most sites:
|
||
|
||
```scss
|
||
// Modern breakpoint values
|
||
$breakpoints: (
|
||
'mobile': 360px, // Small phones
|
||
'tablet': 768px, // Tablets portrait
|
||
'laptop': 1024px, // Small laptops
|
||
'desktop': 1280px, // Standard desktop
|
||
'wide': 1440px // Large screens
|
||
);
|
||
```
|
||
|
||
**Mobile-first remains the consensus** with 58%+ of traffic being mobile. Content-based breakpoints are preferred over device-based—"The moment your layout looks stretched or awkward—that's your first breakpoint."
|
||
|
||
## Modern CSS features ready for production
|
||
|
||
### Production-ready now (use freely)
|
||
|
||
| Feature | Support | Notes |
|
||
|---------|---------|-------|
|
||
| `:has()` selector | 90%+ | The "parent selector" everyone wanted |
|
||
| `@layer` | All modern | Essential for cascade control |
|
||
| Container queries | 90%+ | Component-level responsiveness |
|
||
| CSS Nesting | All modern | Native syntax matches Sass |
|
||
| Subgrid | 78%+ | Safe with graceful degradation |
|
||
| `text-wrap: balance` | 87%+ | Limit: 6 lines Chrome, 10 Firefox |
|
||
| `text-wrap: pretty` | 72%+ | Falls back gracefully |
|
||
| `color-mix()` | All modern | Use with OKLCH |
|
||
| `oklch()` / `oklab()` | All modern | Recommended color format |
|
||
|
||
### Use with caution (progressive enhancement)
|
||
|
||
**`@scope`** has no Firefox support (Nightly only). Use with fallbacks. **View Transitions** work in Chrome/Safari 18+ but Firefox is pending. **Anchor positioning** is Chromium-only (Chrome 125+, Edge 125+)—Firefox and Safari are still in development.
|
||
|
||
### Browser baseline for 2025
|
||
|
||
Target **Chrome 111+, Safari 16.4+, Firefox 128+**. This aligns with Tailwind CSS v4's baseline. IE11 is completely dead—Microsoft ended support in June 2022. No production sites should support IE11.
|
||
|
||
## Cascade layers for design systems
|
||
|
||
`@layer` provides explicit cascade control, eliminating specificity wars with third-party CSS:
|
||
|
||
```scss
|
||
// Declare layer order first—this controls priority
|
||
@layer reset, base, tokens, components, utilities, overrides;
|
||
|
||
@layer reset {
|
||
// Your reset styles (lowest priority)
|
||
}
|
||
|
||
@layer components {
|
||
.button { /* component styles */ }
|
||
}
|
||
|
||
@layer utilities {
|
||
.sr-only { /* highest priority utility */ }
|
||
}
|
||
|
||
// Import third-party CSS into low-priority layer
|
||
@import url('library.css') layer(third-party);
|
||
```
|
||
|
||
Key principle: **layer order equals priority order** (last declared wins). Unlayered styles beat all layered styles—be intentional.
|
||
|
||
## Performance optimization
|
||
|
||
### content-visibility for long pages
|
||
|
||
```scss
|
||
.below-fold-section {
|
||
content-visibility: auto;
|
||
contain-intrinsic-size: auto 600px; // Prevent layout shift
|
||
}
|
||
```
|
||
|
||
Real-world results show **50-80% reduction in initial rendering time** on content-heavy pages. Apply to off-screen, complex content sections—never above-the-fold content (delays LCP).
|
||
|
||
### will-change anti-patterns
|
||
|
||
Never apply `will-change` broadly—it causes performance problems:
|
||
|
||
```scss
|
||
// ❌ Never do this
|
||
* { will-change: transform; }
|
||
|
||
// ✅ Apply dynamically before animation
|
||
.element:hover { will-change: transform; }
|
||
```
|
||
|
||
Use as last resort for existing performance problems, apply to few elements, and remove it when animation completes.
|
||
|
||
### Efficient animation properties
|
||
|
||
```scss
|
||
// ✅ GPU-accelerated, no layout reflow
|
||
.efficient-animation {
|
||
transform: translateX(100px);
|
||
opacity: 0.8;
|
||
filter: blur(2px);
|
||
}
|
||
|
||
// ❌ Avoid animating these (cause reflow)
|
||
.slow-animation {
|
||
width: 200px; // triggers layout
|
||
margin: 1rem; // triggers layout
|
||
}
|
||
```
|
||
|
||
## Accessibility at every screen size
|
||
|
||
### Touch targets: updated guidelines
|
||
|
||
WCAG 2.2 introduced a new AA minimum of **24×24 CSS pixels** (SC 2.5.8), with **44×44px remaining the AAA/best practice** recommendation (SC 2.5.5). Aim for 44×44px on all interactive elements.
|
||
|
||
### Reduced motion implementation
|
||
|
||
Use the **motion-reduce-first approach**—default to no motion, add motion only for users who haven't requested reduced motion:
|
||
|
||
```scss
|
||
// Default: no motion
|
||
.modal-enter {
|
||
opacity: 0;
|
||
}
|
||
|
||
// Add motion for users without preference
|
||
@media (prefers-reduced-motion: no-preference) {
|
||
.modal-enter {
|
||
transform: scale(0.7);
|
||
transition: opacity 0.3s, transform 0.3s;
|
||
}
|
||
}
|
||
```
|
||
|
||
Disable **parallax, large-scale transforms, and zooming completely** for reduced-motion users (vestibular triggers). Micro-interactions can be reduced rather than disabled.
|
||
|
||
### Dark mode without FOUC
|
||
|
||
Prevent Flash of Unstyled Content with an **inline blocking script in `<head>`**:
|
||
|
||
```html
|
||
<html data-theme="light">
|
||
<head>
|
||
<script>
|
||
(function() {
|
||
const stored = localStorage.getItem('theme');
|
||
const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||
const theme = stored || (systemDark ? 'dark' : 'light');
|
||
document.documentElement.setAttribute('data-theme', theme);
|
||
document.documentElement.style.colorScheme = theme;
|
||
})();
|
||
</script>
|
||
</head>
|
||
```
|
||
|
||
### Zoom and reflow requirements
|
||
|
||
Content must work at **200% zoom** (WCAG 2.1). At 400% zoom (equivalent to 320px viewport), content should reflow to single column without horizontal scrolling:
|
||
|
||
```scss
|
||
@media (max-width: 320px) {
|
||
.grid { display: block; }
|
||
}
|
||
```
|
||
|
||
## SCSS module system migration
|
||
|
||
### @import is officially deprecated
|
||
|
||
Dart Sass deprecated `@import` in version 1.80.0 (2024), with removal scheduled for Dart Sass 3.0.0. Use the migration tool: `sass-migrator module --migrate-deps <entrypoint>`.
|
||
|
||
### @use and @forward patterns
|
||
|
||
```scss
|
||
// tokens/_colors.scss
|
||
$primary: oklch(60% 0.2 250);
|
||
$secondary: oklch(55% 0.1 250);
|
||
|
||
// tokens/_index.scss (barrel file)
|
||
@forward 'colors';
|
||
@forward 'spacing';
|
||
@forward 'typography';
|
||
|
||
// components/button.scss
|
||
@use '../tokens';
|
||
|
||
.button {
|
||
background: tokens.$primary;
|
||
padding: tokens.$space-4;
|
||
}
|
||
```
|
||
|
||
### Configuring defaults
|
||
|
||
```scss
|
||
// _theme.scss
|
||
$primary-color: blue !default;
|
||
$border-radius: 4px !default;
|
||
|
||
// main.scss - Override before loading
|
||
@use 'theme' with (
|
||
$primary-color: purple,
|
||
$border-radius: 8px
|
||
);
|
||
```
|
||
|
||
Private members use underscore or dash prefix: `$-private-value` is not accessible externally.
|
||
|
||
## Conclusion
|
||
|
||
Senior-level SCSS architecture in 2025 combines **fluid intrinsic layouts** that work at every pixel width, **container queries** for component-level responsiveness, **OKLCH colors** for perceptually uniform palettes, and **cascade layers** for explicit style priority. The shift is clear: fewer media queries, more fluid techniques; fewer hacks, more native CSS features; fewer SCSS variables, more CSS custom properties for theming.
|
||
|
||
The key insight is that **flawless responsive design comes from intrinsic sizing, not more breakpoints**. Use `repeat(auto-fit, minmax(min(100%, 250px), 1fr))` grids, `clamp()` for typography and spacing, and container queries for component adaptation. Reserve the 3-5 breakpoints for major layout shifts, not incremental adjustments.
|
||
|
||
Test continuously between breakpoints, not just at them. Use Chrome DevTools' drag-to-resize, test at 200% zoom for accessibility, and validate touch targets are at least 44×44px on mobile. With these patterns, layouts scale smoothly from 300px to 1800px+ without awkward in-between states.
|