Skip to content

SCSS Architecture Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Внедрить модульную SCSS-архитектуру (по образцу ban-hummer) с design tokens, fluid typography, 8px spacing grid и responsive mixins.

Architecture: Адаптация 7-1 SCSS паттерна для Tailwind CSS 4 + Nuxt UI v3. SCSS abstracts (переменные, миксины, функции) инжектятся глобально через Vite additionalData. CSS Custom Properties — для runtime-значений (тема, цвета). SCSS-переменные — для compile-time (breakpoints, spacing). Tailwind остаётся для utility-классов, SCSS — для семантических стилей и design tokens.

Tech Stack: Sass (dart-sass), Nuxt 4.3, Tailwind CSS 4, Nuxt UI v3


Target Structure

app/assets/
├── css/
│   └── main.css                    # Tailwind + Nuxt UI imports + custom utilities
└── styles/
    ├── main.scss                   # Entry point — imports base/
    ├── abstracts/
    │   ├── _index.scss             # @forward all partials
    │   ├── _variables.scss         # General vars (header height, transitions)
    │   ├── _typography.scss        # Font sizes, weights, families, line-heights
    │   ├── _spacing.scss           # 8px grid, border-radius
    │   ├── _breakpoints.scss       # Responsive breakpoints (sm-4xl)
    │   ├── _z-index.scss           # Z-index layers
    │   ├── _colors.scss            # SCSS color palette
    │   ├── _mixins.scss            # media-up/down, flex helpers, text-truncate
    │   └── _functions.scss         # fluid-type(), px-to-rem()
    └── base/
        ├── _index.scss             # @use all base partials
        └── _typography.scss        # h1-h6, body, links — fluid-type

Task 1: Install sass dependency

Files:

  • Modify: package.json

Step 1: Install sass

Run: npm install -D sass-embedded Expected: sass-embedded added to devDependencies

Step 2: Commit

bash
git add package.json package-lock.json
git commit -m "chore(DEV-200): add sass-embedded dependency"

Task 2: Create abstracts — functions

Files:

  • Create: app/assets/styles/abstracts/_functions.scss

Step 1: Create the file

scss
@use 'sass:math';

/// Convert px to rem
/// @param $pixels - Value in px
/// @param $base - Base font size (default 16px)
/// @return Value in rem
@function px-to-rem($pixels, $base: 16px) {
  @if math.is-unitless($pixels) {
    $pixels: $pixels * 1px;
  }
  @return math.div($pixels, $base) * 1rem;
}

/// Convert rem to px
@function rem-to-px($rems, $base: 16px) {
  @return math.div($rems, 1rem) * $base;
}

/// Fluid typography using CSS clamp()
/// Linearly scales between min and max size across viewport range
/// @param $min-size - Minimum font size (rem)
/// @param $max-size - Maximum font size (rem)
/// @param $min-width - Min viewport (default: 20rem / 320px)
/// @param $max-width - Max viewport (default: 80rem / 1280px)
@function fluid-type($min-size, $max-size, $min-width: 20rem, $max-width: 80rem) {
  $slope: math.div($max-size - $min-size, $max-width - $min-width);
  $intercept: $min-size - $slope * $min-width;

  @return clamp(#{$min-size}, #{$intercept} + #{$slope * 100vw}, #{$max-size});
}

/// Get spacing value from map
/// @param $key - Spacer key (0-12)
@function spacing($key) {
  @return map-get($spacers, $key);
}

/// Strip unit from a number
@function strip-unit($number) {
  @if type-of($number) == 'number' and not math.is-unitless($number) {
    @return math.div($number, $number * 0 + 1);
  }
  @return $number;
}

Step 2: Commit

bash
git add app/assets/styles/abstracts/_functions.scss
git commit -m "feat(DEV-200): add SCSS functions (fluid-type, px-to-rem)"

Task 3: Create abstracts — variables, typography, spacing, breakpoints, z-index, colors

Files:

  • Create: app/assets/styles/abstracts/_variables.scss
  • Create: app/assets/styles/abstracts/_typography.scss
  • Create: app/assets/styles/abstracts/_spacing.scss
  • Create: app/assets/styles/abstracts/_breakpoints.scss
  • Create: app/assets/styles/abstracts/_z-index.scss
  • Create: app/assets/styles/abstracts/_colors.scss

Step 1: Create _variables.scss

scss
// Layout
$header-height: 3.5rem;       // 56px
$sidebar-width: 16rem;        // 256px
$container-max-width: 80rem;  // 1280px

// Transitions
$transition-fast: 150ms ease;
$transition-base: 200ms ease-in-out;
$transition-slow: 300ms ease-in-out;

// Opacity
$opacity-disabled: 0.5;
$opacity-hover: 0.8;

Step 2: Create _typography.scss

scss
// Font families
$font-family-primary: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
$font-family-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;

// Font weights
$font-weight-normal: 400;
$font-weight-medium: 500;
$font-weight-semibold: 600;
$font-weight-bold: 700;

// Font sizes (rem, based on 16px root)
// Aligned with wireframes: body 14-16px, headings 18-36px
$font-size-xs: 0.75rem;     // 12px — captions, badges
$font-size-sm: 0.875rem;    // 14px — secondary text, nav
$font-size-base: 1rem;      // 16px — body
$font-size-md: 1.125rem;    // 18px — card titles
$font-size-lg: 1.25rem;     // 20px — prices, subheadings
$font-size-xl: 1.5rem;      // 24px — section headings (h2)
$font-size-2xl: 1.875rem;   // 30px — h1 (mobile)
$font-size-3xl: 2.25rem;    // 36px — h1 (desktop), hero

// Heading sizes (fluid)
// These use fluid-type() in base/_typography.scss
$h1-size-min: 1.875rem;   // 30px mobile
$h1-size-max: 2.25rem;    // 36px desktop
$h2-size-min: 1.25rem;    // 20px mobile
$h2-size-max: 1.5rem;     // 24px desktop
$h3-size-min: 1rem;       // 16px mobile
$h3-size-max: 1.25rem;    // 20px desktop

// Line heights
$line-height-none: 1;
$line-height-tight: 1.25;
$line-height-snug: 1.375;
$line-height-normal: 1.5;
$line-height-relaxed: 1.625;

// Letter spacing
$letter-spacing-tight: -0.025em;
$letter-spacing-normal: 0;
$letter-spacing-wide: 0.025em;

Step 3: Create _spacing.scss

scss
// 8px base grid
$spacer: 0.5rem; // 8px

$spacers: (
  0: 0,
  1: 0.125rem,   // 2px
  2: 0.25rem,    // 4px
  3: 0.5rem,     // 8px
  4: 0.75rem,    // 12px
  5: 1rem,       // 16px
  6: 1.25rem,    // 20px
  7: 1.5rem,     // 24px
  8: 2rem,       // 32px
  10: 3rem,      // 48px
  12: 4rem,      // 64px
);

// Semantic spacing shortcuts
$spacing-xs: 0.25rem;   // 4px
$spacing-sm: 0.5rem;    // 8px
$spacing-md: 1rem;      // 16px
$spacing-lg: 1.5rem;    // 24px
$spacing-xl: 2rem;      // 32px
$spacing-2xl: 3rem;     // 48px

// Border radius
$radius-sm: 0.25rem;    // 4px
$radius: 0.5rem;        // 8px
$radius-md: 0.75rem;    // 12px
$radius-lg: 1rem;       // 16px
$radius-xl: 1.5rem;     // 24px
$radius-full: 9999px;

Step 4: Create _breakpoints.scss

scss
// Aligned with Tailwind defaults
$breakpoints: (
  'sm': 40rem,     // 640px
  'md': 48rem,     // 768px
  'lg': 64rem,     // 1024px
  'xl': 80rem,     // 1280px
  '2xl': 96rem,    // 1536px
  '3xl': 112.5rem, // 1800px — ultra-HD
  '4xl': 120rem,   // 1920px — full HD
);

// Container max-widths per breakpoint
$container-max-widths: (
  'sm': 38rem,     // 608px
  'md': 45rem,     // 720px
  'lg': 60rem,     // 960px
  'xl': 72rem,     // 1152px
  '2xl': 80rem,    // 1280px
);

Step 5: Create _z-index.scss

scss
$z-index-dropdown: 1000;
$z-index-sticky: 1020;
$z-index-fixed: 1030;
$z-index-modal-backdrop: 1040;
$z-index-modal: 1050;
$z-index-popover: 1060;
$z-index-tooltip: 1070;
$z-index-notification: 1080;

Step 6: Create _colors.scss

scss
// Brand
$color-primary: #3b82f6;       // blue-500 (matches Nuxt UI primary)
$color-primary-hover: #2563eb; // blue-600

// Semantic
$color-success: #10b981;   // emerald-500
$color-warning: #f59e0b;   // amber-500
$color-error: #ef4444;     // red-500
$color-info: #3b82f6;      // blue-500

// Neutral (for SCSS usage, runtime via Nuxt UI CSS vars)
$color-text: #1e293b;        // slate-800
$color-text-muted: #64748b;  // slate-500
$color-border: #e2e8f0;      // slate-200
$color-bg: #ffffff;
$color-surface: #f8fafc;     // slate-50

Step 7: Commit

bash
git add app/assets/styles/abstracts/
git commit -m "feat(DEV-200): add SCSS abstracts (typography, spacing, breakpoints, z-index, colors)"

Task 4: Create abstracts — mixins and index

Files:

  • Create: app/assets/styles/abstracts/_mixins.scss
  • Create: app/assets/styles/abstracts/_index.scss

Step 1: Create _mixins.scss

scss
@use 'sass:map';
@use 'breakpoints' as *;

// ── Responsive ──

@mixin media-up($breakpoint) {
  @if map.has-key($breakpoints, $breakpoint) {
    @media (min-width: map.get($breakpoints, $breakpoint)) {
      @content;
    }
  } @else {
    @error "Unknown breakpoint: #{$breakpoint}. Available: #{map.keys($breakpoints)}";
  }
}

@mixin media-down($breakpoint) {
  @if map.has-key($breakpoints, $breakpoint) {
    @media (max-width: calc(#{map.get($breakpoints, $breakpoint)} - 0.0625rem)) {
      @content;
    }
  } @else {
    @error "Unknown breakpoint: #{$breakpoint}.";
  }
}

// ── Layout ──

@mixin flex-center {
  display: flex;
  align-items: center;
  justify-content: center;
}

@mixin flex-between {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

// ── Text ──

@mixin text-truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@mixin text-clamp($lines: 2) {
  display: -webkit-box;
  -webkit-line-clamp: $lines;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

// ── Interactive ──

@mixin focus-ring($color: var(--ui-primary)) {
  outline: 2px solid $color;
  outline-offset: 2px;
}

@mixin transition($properties: all, $duration: 0.2s, $easing: ease-in-out) {
  transition: $properties $duration $easing;
}

Step 2: Create _index.scss (global export)

scss
// Order matters: functions first (used by other partials)
@forward 'functions';
@forward 'colors';
@forward 'typography';
@forward 'spacing';
@forward 'breakpoints';
@forward 'z-index';
@forward 'variables';
@forward 'mixins';

Step 3: Commit

bash
git add app/assets/styles/abstracts/
git commit -m "feat(DEV-200): add SCSS mixins and abstracts index"

Task 5: Create base typography with fluid-type

Files:

  • Create: app/assets/styles/base/_typography.scss
  • Create: app/assets/styles/base/_index.scss
  • Create: app/assets/styles/main.scss

Step 1: Create base/_typography.scss

scss
@use '../abstracts' as *;

// ── Headings ──

h1, .h1 {
  font-size: fluid-type($h1-size-min, $h1-size-max);
  font-weight: $font-weight-bold;
  line-height: $line-height-tight;
  letter-spacing: $letter-spacing-tight;
}

h2, .h2 {
  font-size: fluid-type($h2-size-min, $h2-size-max);
  font-weight: $font-weight-bold;
  line-height: $line-height-tight;
}

h3, .h3 {
  font-size: fluid-type($h3-size-min, $h3-size-max);
  font-weight: $font-weight-semibold;
  line-height: $line-height-snug;
}

// ── Body ──

body {
  font-family: $font-family-primary;
  font-size: $font-size-base;
  line-height: $line-height-normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

// ── Links ──

a {
  text-decoration-skip-ink: auto;
}

// ── Code ──

code, kbd, pre, samp {
  font-family: $font-family-mono;
}

// ── Small text ──

small, .text-small {
  font-size: $font-size-sm;
  line-height: $line-height-normal;
}

Step 2: Create base/_index.scss

scss
@use 'typography';

Step 3: Create main.scss

scss
@use 'base';

Step 4: Commit

bash
git add app/assets/styles/
git commit -m "feat(DEV-200): add base typography with fluid-type headings"

Task 6: Configure Vite SCSS injection + register main.scss

Files:

  • Modify: nuxt.config.ts

Step 1: Add SCSS global injection and register main.scss

In nuxt.config.ts, add to the css array and configure vite.css:

typescript
css: ['~/assets/css/main.css', '~/assets/styles/main.scss'],

And add Vite SCSS preprocessor config:

typescript
vite: {
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: '@use "~/assets/styles/abstracts" as *;\n',
        api: 'modern-compiler',
      },
    },
  },
  // ... existing vite config
},

Step 2: Run dev server to verify

Run: rm -rf .nuxt && npm run dev Expected: Server starts without SCSS compilation errors

Step 3: Verify in browser — check h1 has fluid-type

Navigate to http://localhost:3000 Check: getComputedStyle(document.querySelector('h1')).fontSize returns value between 30-36px

Step 4: Commit

bash
git add nuxt.config.ts
git commit -m "feat(DEV-200): configure Vite SCSS global injection and register styles"

Task 7: Verify all pages render correctly

Step 1: Check homepage

Run: curl -s -o /dev/null -w '%{http_code}' http://localhost:3000/ Expected: 200

Step 2: Check catalog

Run: curl -s -o /dev/null -w '%{http_code}' http://localhost:3000/catalog Expected: 200

Step 3: Check auth pages (SSR)

Run: curl -s -o /dev/null -w '%{http_code}' http://localhost:3000/auth/login Expected: 200

Step 4: Run lint

Run: npm run lint Expected: No errors

Step 5: Run typecheck

Run: npm run typecheck Expected: No errors

Step 6: Final commit with all typography + SCSS architecture

bash
git add -A
git commit -m "feat(DEV-200): complete SCSS architecture with fluid typography"