Cybersecurity-Projects/PROJECTS/bug-bounty-platform/docs/research/SCSS.md

20 KiB
Raw Blame History

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:

// _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.

// _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.

// 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.

// 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

: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

// _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

@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

@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

// 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:

{
  "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.

// 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).

: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:

// 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:

// 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:

// 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

.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:

// ❌ 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

// ✅ 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:

// 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 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:

@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

// 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

// _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.