Framework and Library Integration
1. Bootstrap SCSS Customization
| Feature | Customization Method | File/Variable | Impact |
|---|---|---|---|
| Theme Colors | Override $theme-colors map | _variables.scss | Primary, secondary, etc. |
| Grid System | Configure breakpoints/columns | $grid-breakpoints | Responsive layout |
| Typography | Font family/size variables | $font-family-base | Global text styles |
| Spacing Scale | $spacer variable | margin/padding utilities | Consistent spacing |
| Component Imports | Selective @import | bootstrap.scss | Reduced bundle size |
Example: Bootstrap customization setup
// custom-bootstrap.scss
// 1. Override default variables BEFORE importing Bootstrap
$primary: #007bff;
$secondary: #6c757d;
$success: #28a745;
$danger: #dc3545;
$warning: #ffc107;
$info: #17a2b8;
// Override theme colors map
$theme-colors: (
"primary": $primary,
"secondary": $secondary,
"success": $success,
"danger": $danger,
"warning": $warning,
"info": $info,
"light": #f8f9fa,
"dark": #343a40,
"custom": #5a67d8 // Add custom color
);
// Grid customization
$grid-breakpoints: (
xs: 0,
sm: 576px,
md: 768px,
lg: 992px,
xl: 1200px,
xxl: 1400px
);
$container-max-widths: (
sm: 540px,
md: 720px,
lg: 960px,
xl: 1140px,
xxl: 1320px
);
// Typography
$font-family-sans-serif: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
$font-size-base: 1rem;
$line-height-base: 1.5;
// Spacing
$spacer: 1rem;
$spacers: (
0: 0,
1: $spacer * 0.25,
2: $spacer * 0.5,
3: $spacer,
4: $spacer * 1.5,
5: $spacer * 3,
6: $spacer * 4, // Custom
7: $spacer * 5 // Custom
);
// Border radius
$border-radius: 0.375rem;
$border-radius-sm: 0.25rem;
$border-radius-lg: 0.5rem;
// 2. Import Bootstrap
@import "~bootstrap/scss/bootstrap";
// 3. Add custom extensions AFTER Bootstrap
.btn-custom {
@include button-variant($custom, darken($custom, 7.5%));
}
// Custom utilities
.bg-gradient-primary {
background: linear-gradient(135deg, $primary, darken($primary, 15%));
}
Example: Selective Bootstrap imports for smaller bundles
// minimal-bootstrap.scss
// 1. Include functions first (required)
@import "~bootstrap/scss/functions";
// 2. Include variable overrides
$primary: #007bff;
$enable-shadows: true;
$enable-gradients: false;
// 3. Include required Bootstrap files
@import "~bootstrap/scss/variables";
@import "~bootstrap/scss/mixins";
@import "~bootstrap/scss/root";
// 4. Include only needed components (selective imports)
@import "~bootstrap/scss/reboot";
@import "~bootstrap/scss/type";
@import "~bootstrap/scss/grid";
@import "~bootstrap/scss/containers";
@import "~bootstrap/scss/buttons";
@import "~bootstrap/scss/forms";
@import "~bootstrap/scss/utilities";
// Skip components you don't need:
// @import "~bootstrap/scss/dropdown";
// @import "~bootstrap/scss/modal";
// @import "~bootstrap/scss/carousel";
// @import "~bootstrap/scss/spinners";
// etc.
// 5. Include utilities API (for custom utilities)
@import "~bootstrap/scss/utilities/api";
// 6. Custom utilities
$utilities: map-merge(
$utilities,
(
"cursor": (
property: cursor,
class: cursor,
values: pointer grab grabbing not-allowed
),
"opacity": (
property: opacity,
values: (
0: 0,
25: .25,
50: .5,
75: .75,
100: 1
)
)
)
);
Example: Advanced Bootstrap customization patterns
// advanced-bootstrap-custom.scss
// Custom color system
$custom-colors: (
"brand-blue": #0d6efd,
"brand-purple": #6f42c1,
"brand-pink": #d63384,
"brand-orange": #fd7e14
);
// Merge custom colors with theme colors
$theme-colors: map-merge($theme-colors, $custom-colors);
// Generate color variants
@each $color, $value in $theme-colors {
.bg-#{$color}-subtle {
background-color: rgba($value, 0.1);
}
.border-#{$color}-subtle {
border-color: rgba($value, 0.3);
}
.text-#{$color}-dark {
color: darken($value, 15%);
}
}
// Custom button sizes
$btn-padding-y-xs: 0.125rem;
$btn-padding-x-xs: 0.5rem;
$btn-font-size-xs: 0.75rem;
.btn-xs {
@include button-size(
$btn-padding-y-xs,
$btn-padding-x-xs,
$btn-font-size-xs,
$border-radius-sm
);
}
// Extend Bootstrap mixins
@mixin custom-button-outline-variant($color) {
@include button-outline-variant($color);
&:hover {
box-shadow: 0 4px 8px rgba($color, 0.3);
transform: translateY(-2px);
}
}
.btn-outline-custom {
@include custom-button-outline-variant($custom);
}
// Custom form controls
$input-focus-border-color: $primary;
$input-focus-box-shadow: 0 0 0 0.25rem rgba($primary, 0.25);
// Card customizations
$card-border-radius: $border-radius-lg;
$card-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
.card-hover {
@extend .card;
transition: transform 0.2s, box-shadow 0.2s;
&:hover {
transform: translateY(-4px);
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}
}
2. Foundation Framework Integration
| Feature | Configuration | File/Setting | Purpose |
|---|---|---|---|
| Foundation Settings | $foundation-palette | _settings.scss | Color system |
| Grid Configuration | xy-grid or float grid | Grid mixins | Layout system |
| Motion UI | Animation library | motion-ui settings | Transitions/animations |
| Component Sass | Selective imports | foundation.scss | Tree shaking |
| Breakpoint Mgmt | $breakpoints map | Responsive helpers | Media queries |
Example: Foundation customization setup
// custom-foundation.scss
// 1. Import Foundation settings (copy from node_modules)
@import 'foundation-sites/scss/foundation';
// 2. Override Foundation variables
$foundation-palette: (
primary: #1779ba,
secondary: #767676,
success: #3adb76,
warning: #ffae00,
alert: #cc4b37,
custom: #5e35b1
);
// Grid settings
$grid-row-width: 1200px;
$grid-column-count: 12;
$grid-column-gutter: 30px;
// Breakpoints
$breakpoints: (
small: 0,
medium: 640px,
large: 1024px,
xlarge: 1200px,
xxlarge: 1440px
);
// Typography
$header-font-family: 'Roboto', sans-serif;
$body-font-family: 'Open Sans', sans-serif;
$global-font-size: 16px;
$global-lineheight: 1.5;
// 3. Include Foundation components (selective)
@include foundation-global-styles;
@include foundation-xy-grid-classes;
@include foundation-typography;
@include foundation-button;
@include foundation-forms;
@include foundation-visibility-classes;
@include foundation-float-classes;
@include foundation-flex-classes;
// Skip unneeded components to reduce size
// @include foundation-accordion;
// @include foundation-badge;
// @include foundation-breadcrumbs;
// @include foundation-card;
// etc.
// 4. Custom extensions
.button.custom {
background-color: map-get($foundation-palette, custom);
&:hover {
background-color: darken(map-get($foundation-palette, custom), 10%);
}
}
Example: Foundation XY Grid customization
// foundation-xy-grid-custom.scss
@import 'foundation-sites/scss/foundation';
// XY Grid settings
$xy-grid: true;
$grid-container: 1200px;
$grid-columns: 12;
$grid-margin-gutters: (
small: 20px,
medium: 30px
);
$grid-padding-gutters: $grid-margin-gutters;
// Custom grid classes
@include foundation-xy-grid-classes(
$base-grid: true,
$margin-grid: true,
$padding-grid: true,
$block-grid: true,
$collapse: true,
$offset: true,
$vertical-grid: true,
$frame-grid: false
);
// Custom grid utilities
@mixin custom-grid-container {
@include xy-grid-container;
padding-left: 1rem;
padding-right: 1rem;
@include breakpoint(large) {
padding-left: 2rem;
padding-right: 2rem;
}
}
.custom-container {
@include custom-grid-container;
}
// Responsive grid mixins
@mixin responsive-grid($columns) {
@include xy-grid;
@each $size, $width in $breakpoints {
@include breakpoint($size) {
@include xy-grid-layout(
map-get($columns, $size),
'.cell'
);
}
}
}
// Usage
.product-grid {
@include responsive-grid((
small: 1,
medium: 2,
large: 3,
xlarge: 4
));
}
Example: Foundation Motion UI integration
// foundation-motion-ui.scss
@import 'foundation-sites/scss/foundation';
@import 'motion-ui/motion-ui';
// Motion UI settings
$motion-ui-speeds: (
default: 500ms,
slow: 750ms,
fast: 250ms
);
$motion-ui-easings: (
linear: linear,
ease: ease,
ease-in: ease-in,
ease-out: ease-out,
ease-in-out: ease-in-out,
bounce: cubic-bezier(0.5, 1.8, 0.9, 0.8)
);
// Include Motion UI
@include motion-ui-transitions;
@include motion-ui-animations;
// Custom animations using Motion UI
.fade-slide-in {
@include mui-animation(fade);
@include mui-animation(slide);
}
.scale-and-fade {
@include mui-series {
@include mui-animation(fade(in, 0, 1));
@include mui-animation(scale(in, 0.5, 1));
}
}
// Custom slide variants
@include mui-slide(
$state: in,
$direction: down,
$amount: 100%
);
.slide-in-down {
@include mui-animation(slide(in, down));
}
// Hinge animation
.hinge-out {
@include mui-hinge(
$state: out,
$from: top,
$axis: edge,
$perspective: 2000px,
$turn-origin: from-back
);
}
// Queue animations
.complex-entrance {
@include mui-queue(
fade(in),
slide(in, up, 50px),
spin(in, cw, 1turn)
);
@include mui-duration(1s);
@include mui-timing(ease-out);
}
3. CSS-in-JS Library Compatibility
| Library | SCSS Integration | Approach | Trade-offs |
|---|---|---|---|
| Styled Components | sass plugin/babel-plugin | Hybrid approach | Build complexity |
| Emotion | @emotion/css with SCSS | Preprocessor + runtime | Two style systems |
| JSS | jss-plugin-nested | Similar syntax | Learning curve |
| Linaria | Zero-runtime CSS-in-JS | Build-time extraction | Limited dynamic styles |
| Vanilla Extract | TypeScript CSS modules | Type-safe styles | No runtime theming |
Example: SCSS utilities for CSS-in-JS migration
// scss-to-js-helpers.scss
// Export SCSS variables to JavaScript
:export {
primary: $primary;
secondary: $secondary;
success: $success;
danger: $danger;
// ... other variables
}
// Alternative: Use CSS custom properties for runtime access
:root {
--color-primary: #{$primary};
--color-secondary: #{$secondary};
--spacing-sm: #{$spacing-sm};
--spacing-md: #{$spacing-md};
--spacing-lg: #{$spacing-lg};
--border-radius: #{$border-radius};
@each $name, $value in $breakpoints {
--breakpoint-#{$name}: #{$value};
}
}
// Mixin to convert SCSS styles to CSS custom properties
@mixin export-as-css-vars($map, $prefix: '') {
@each $key, $value in $map {
@if type-of($value) == 'map' {
@include export-as-css-vars($value, '#{$prefix}#{$key}-');
} @else {
--#{$prefix}#{$key}: #{$value};
}
}
}
// Theme object for CSS-in-JS
$theme-export: (
colors: (
primary: $primary,
secondary: $secondary,
text: $text-color,
background: $bg-color
),
spacing: (
xs: 0.25rem,
sm: 0.5rem,
md: 1rem,
lg: 1.5rem,
xl: 2rem
),
typography: (
fontFamily: $font-family-base,
fontSize: (
sm: 0.875rem,
base: 1rem,
lg: 1.25rem,
xl: 1.5rem
)
)
);
:root {
@include export-as-css-vars($theme-export);
}
// JavaScript usage:
// const primary = getComputedStyle(document.documentElement)
// .getPropertyValue('--colors-primary');
Example: Styled Components with SCSS mixins
// styles/mixins.scss - Shared SCSS mixins
@mixin flex-center {
display: flex;
align-items: center;
justify-content: center;
}
@mixin button-base {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
&:hover {
transform: translateY(-2px);
}
}
@mixin responsive-font($min-size, $max-size, $min-width: 320px, $max-width: 1200px) {
font-size: $min-size;
@media (min-width: $min-width) {
font-size: calc(
#{$min-size} +
(#{strip-unit($max-size)} - #{strip-unit($min-size)}) *
(100vw - #{$min-width}) /
(#{strip-unit($max-width)} - #{strip-unit($min-width)})
);
}
@media (min-width: $max-width) {
font-size: $max-size;
}
}
// Component using Styled Components + SCSS
// Button.jsx
import styled from 'styled-components';
export const Button = styled.button`
${props => props.theme.mixins.buttonBase}
background-color: ${props => props.theme.colors.primary};
color: white;
&:hover {
background-color: ${props => props.theme.colors.primaryDark};
}
${props => props.variant === 'outline' && `
background-color: transparent;
border: 2px solid ${props.theme.colors.primary};
color: ${props.theme.colors.primary};
`}
`;
// Theme object combining SCSS values
// theme.js
import scssVariables from './styles/variables.scss';
export const theme = {
colors: {
primary: scssVariables.primary,
secondary: scssVariables.secondary,
// ... other colors
},
spacing: {
xs: '0.25rem',
sm: '0.5rem',
md: '1rem',
lg: '1.5rem',
},
mixins: {
buttonBase: `
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
`
}
};
Example: Migrating SCSS utilities to CSS-in-JS
// SCSS original
// utilities.scss
@mixin truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@mixin visually-hidden {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
// CSS-in-JS equivalent (Emotion/Styled Components)
// utilities.js
export const truncate = css`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
export const visuallyHidden = css`
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
`;
// Or as styled-components helper
import { css } from 'styled-components';
export const mixins = {
truncate: () => css`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`,
visuallyHidden: () => css`
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
`,
responsive: (minWidth, styles) => css`
@media (min-width: ${minWidth}) {
${styles}
}
`
};
// Usage in component
import styled from 'styled-components';
import { mixins } from './utilities';
const Title = styled.h1`
${mixins.truncate()}
color: ${props => props.theme.colors.primary};
${mixins.responsive('768px', css`
font-size: 2rem;
`)}
`;
4. React/Vue/Angular Component Styling
| Framework | SCSS Integration | Scoping Method | Best Practice |
|---|---|---|---|
| React | CSS Modules / Sass loader | Automatic class hashing | Component co-location |
| Vue | <style lang="scss"> | scoped attribute | Single File Components |
| Angular | styleUrls / Sass in CLI | ViewEncapsulation | Component styles array |
| Svelte | <style lang="scss"> | Auto-scoped styles | Component-level SCSS |
| Next.js | CSS Modules (.module.scss) | Local scoping | Global + module mix |
Example: React with CSS Modules and SCSS
// Button.module.scss
@import '../../styles/variables';
@import '../../styles/mixins';
.button {
@include button-base;
background-color: $primary;
color: white;
&:hover {
background-color: darken($primary, 10%);
}
// Modifier classes
&.outline {
background-color: transparent;
border: 2px solid $primary;
color: $primary;
&:hover {
background-color: $primary;
color: white;
}
}
&.large {
padding: 1rem 2rem;
font-size: 1.25rem;
}
&.small {
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
}
&.disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
}
.icon {
margin-right: 0.5rem;
.button.iconOnly & {
margin-right: 0;
}
}
// Button.jsx
import React from 'react';
import styles from './Button.module.scss';
import classNames from 'classnames';
export const Button = ({
children,
variant = 'primary',
size = 'medium',
disabled = false,
icon,
iconOnly = false,
...props
}) => {
return (
<button
className={classNames(
styles.button,
styles[variant],
styles[size],
{ [styles.disabled]: disabled },
{ [styles.iconOnly]: iconOnly }
)}
disabled={disabled}
{...props}
>
{icon && <span className={styles.icon}>{icon}</span>}
{!iconOnly && children}
</button>
);
};
// Usage
<Button variant="outline" size="large">Click Me</Button>
Example: Vue Single File Component with SCSS
<!-- Card.vue -->
<template>
<div :class="['card', `card--${variant}`, { 'card--hoverable': hoverable }]">
<div v-if="$slots.header" class="card__header">
<slot name="header"></slot>
</div>
<div class="card__body">
<slot></slot>
</div>
<div v-if="$slots.footer" class="card__footer">
<slot name="footer"></slot>
</div>
</div>
</template>
<script>
export default {
name: 'Card',
props: {
variant: {
type: String,
default: 'default',
validator: (value) => ['default', 'primary', 'success', 'danger'].includes(value)
},
hoverable: {
type: Boolean,
default: false
}
}
};
</script>
<style lang="scss" scoped>
@import '@/styles/variables';
@import '@/styles/mixins';
.card {
background-color: $card-bg;
border: 1px solid $border-color;
border-radius: $border-radius;
overflow: hidden;
&__header {
padding: $spacing-md;
border-bottom: 1px solid $border-color;
font-weight: 600;
background-color: $gray-50;
}
&__body {
padding: $spacing-md;
}
&__footer {
padding: $spacing-md;
border-top: 1px solid $border-color;
background-color: $gray-50;
}
// Variants
&--primary {
border-color: $primary;
.card__header {
background-color: $primary;
color: white;
border-bottom-color: darken($primary, 10%);
}
}
&--success {
border-color: $success;
.card__header {
background-color: $success;
color: white;
}
}
&--danger {
border-color: $danger;
.card__header {
background-color: $danger;
color: white;
}
}
// Hoverable state
&--hoverable {
@include card-hover-effect;
cursor: pointer;
}
}
// Deep selector for slot content styling
::v-deep {
.card__body {
p:last-child {
margin-bottom: 0;
}
}
}
</style>
<!-- Usage -->
<Card variant="primary" hoverable>
<template #header>Card Title</template>
<p>Card content goes here</p>
<template #footer>Card Footer</template>
</Card>
Example: Angular component styling with SCSS
// alert.component.scss
@import 'src/styles/variables';
@import 'src/styles/mixins';
:host {
display: block;
margin-bottom: $spacing-md;
}
.alert {
@include alert-base;
padding: $spacing-md;
border-radius: $border-radius;
border-left: 4px solid transparent;
position: relative;
&__icon {
margin-right: $spacing-sm;
vertical-align: middle;
}
&__close {
@include button-reset;
position: absolute;
top: $spacing-sm;
right: $spacing-sm;
font-size: 1.25rem;
opacity: 0.5;
cursor: pointer;
&:hover {
opacity: 1;
}
}
&__title {
font-weight: 600;
margin-bottom: $spacing-xs;
}
&__message {
margin: 0;
}
// Variants using maps
@each $variant, $color in $alert-colors {
&--#{$variant} {
background-color: lighten($color, 45%);
border-left-color: $color;
color: darken($color, 20%);
.alert__icon {
color: $color;
}
}
}
}
// alert.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-alert',
templateUrl: './alert.component.html',
styleUrls: ['./alert.component.scss'],
// ViewEncapsulation options:
// - Emulated (default): Scoped styles with attribute selectors
// - None: Global styles
// - ShadowDom: True Shadow DOM encapsulation
})
export class AlertComponent {
@Input() variant: 'info' | 'success' | 'warning' | 'danger' = 'info';
@Input() title?: string;
@Input() dismissible = false;
@Output() dismiss = new EventEmitter<void>();
onClose(): void {
this.dismiss.emit();
}
}
// alert.component.html
<div class="alert alert--{{ variant }}">
<span class="alert__icon">
<!-- Icon SVG or component -->
</span>
<div class="alert__content">
<div *ngIf="title" class="alert__title">{{ title }}</div>
<p class="alert__message"><ng-content></ng-content></p>
</div>
<button
*ngIf="dismissible"
class="alert__close"
(click)="onClose()"
aria-label="Close"
>
×
</button>
</div>
// Usage
<app-alert variant="success" title="Success!" [dismissible]="true">
Your changes have been saved.
</app-alert>
5. Styled Components vs SCSS Comparison
| Feature | SCSS | Styled Components | Winner |
|---|---|---|---|
| Performance | Build-time compilation | Runtime style injection | SCSS |
| Bundle Size | Static CSS file | ~15kb library overhead | SCSS |
| Dynamic Theming | CSS custom properties | Theme provider + props | Styled Components |
| Type Safety | None (unless typed tokens) | TypeScript integration | Styled Components |
| Learning Curve | CSS knowledge | CSS + JS patterns | SCSS |
| Component Scoping | BEM or CSS Modules | Automatic scoping | Styled Components |
| SSR Support | Native | Requires setup | SCSS |
| Conditional Styles | Classes or mixins | Props interpolation | Styled Components |
Example: Same component in SCSS vs Styled Components
// ===== SCSS Approach =====
// Button.module.scss
@import 'variables';
.button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
&:hover {
transform: translateY(-2px);
}
}
.primary {
background-color: $primary;
color: white;
}
.secondary {
background-color: $secondary;
color: white;
}
.large {
padding: 1rem 2rem;
font-size: 1.25rem;
}
.small {
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
}
// Button.jsx
import styles from './Button.module.scss';
import classNames from 'classnames';
const Button = ({ variant = 'primary', size = 'medium', children }) => (
<button className={classNames(
styles.button,
styles[variant],
styles[size]
)}>
{children}
</button>
);
// ===== Styled Components Approach =====
import styled from 'styled-components';
const Button = styled.button`
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
/* Dynamic styles based on props */
background-color: ${props =>
props.variant === 'primary' ? props.theme.colors.primary :
props.variant === 'secondary' ? props.theme.colors.secondary :
props.theme.colors.default
};
color: white;
/* Size variants */
${props => props.size === 'large' && `
padding: 1rem 2rem;
font-size: 1.25rem;
`}
${props => props.size === 'small' && `
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
`}
&:hover {
transform: translateY(-2px);
}
`;
// Usage is the same
<Button variant="primary" size="large">Click Me</Button>
// ===== Hybrid Approach: Best of Both Worlds =====
// Use SCSS for static styles, Styled Components for dynamic
import styled from 'styled-components';
import './Button.scss'; // Import base SCSS styles
const DynamicButton = styled.button.attrs(props => ({
className: `button button--${props.variant} button--${props.size}`
}))`
/* Only dynamic, component-specific overrides */
${props => props.highlighted && `
box-shadow: 0 0 0 3px ${props.theme.colors.highlight};
`}
${props => props.loading && `
opacity: 0.6;
cursor: wait;
`}
`;
Example: Performance considerations
// SCSS - Build-time optimization
// All styles compiled to static CSS
// Output: button.abc123.css (minified, cached)
.button { /* ... */ }
.button--primary { /* ... */ }
.button--large { /* ... */ }
// Pros:
// ✅ No runtime overhead
// ✅ Better initial load performance
// ✅ Works without JavaScript
// ✅ Easier to debug in DevTools
// ✅ Better caching (static file)
// Cons:
// ❌ Less dynamic (need CSS vars for theming)
// ❌ Manual scoping (BEM, modules)
// ❌ Harder to use component props
// ❌ No type safety
// ===================================
// Styled Components - Runtime styles
import styled from 'styled-components';
const Button = styled.button`
background: ${p => p.theme.colors[p.variant]};
`;
// Generated at runtime:
// <style data-styled="active">
// .sc-bdnylx { /* ... */ }
// </style>
// Pros:
// ✅ Fully dynamic (props, theme)
// ✅ Automatic scoping
// ✅ TypeScript support
// ✅ Co-located with component
// ✅ Dead code elimination
// Cons:
// ❌ Runtime performance cost
// ❌ Larger bundle size (~15kb)
// ❌ SSR complexity
// ❌ Styles in JS bundle (not cached separately)
// ❌ Flash of unstyled content (FOUC)
// ===================================
// Best Practice: Hybrid Approach
// Use SCSS for:
// - Global styles and resets
// - Design system tokens
// - Static utility classes
// - Third-party component overrides
// Use Styled Components for:
// - Highly dynamic components
// - Component-specific styles
// - Theme-dependent UI
// - Type-safe style APIs
// Example hybrid setup:
// _global.scss - Global styles
@import 'reset';
@import 'variables';
@import 'utilities';
// Component with hybrid approach
import styled from 'styled-components';
import './Button.scss'; // Base styles
const Button = styled.button`
/* Inherit base .button class from SCSS */
${props => props.dynamic && `
/* Only add dynamic overrides */
background: ${props.theme.colors[props.variant]};
`}
`;
6. Design Token Integration (Style Dictionary)
| Feature | Implementation | Output | Use Case |
|---|---|---|---|
| Token Definition | JSON/YAML design tokens | Platform-agnostic source | Single source of truth |
| SCSS Transform | Style Dictionary build | SCSS variables | Web styling |
| CSS Custom Props | Custom format template | :root with --vars | Runtime theming |
| Multi-platform | iOS, Android, Web outputs | Native + web tokens | Cross-platform apps |
| Figma Sync | figma-tokens plugin | Automated updates | Design-dev sync |
Example: Style Dictionary configuration
// config.json - Style Dictionary configuration
{
"source": ["tokens/**/*.json"],
"platforms": {
"scss": {
"transformGroup": "scss",
"buildPath": "src/styles/",
"files": [
{
"destination": "_tokens.scss",
"format": "scss/variables"
},
{
"destination": "_tokens-map.scss",
"format": "scss/map-deep"
}
]
},
"css": {
"transformGroup": "css",
"buildPath": "src/styles/",
"files": [
{
"destination": "tokens.css",
"format": "css/variables"
}
]
}
}
}
// tokens/color.json - Design tokens
{
"color": {
"brand": {
"primary": { "value": "#0066cc" },
"secondary": { "value": "#6c757d" }
},
"semantic": {
"success": { "value": "#28a745" },
"danger": { "value": "#dc3545" },
"warning": { "value": "#ffc107" },
"info": { "value": "#17a2b8" }
},
"neutral": {
"100": { "value": "#f8f9fa" },
"200": { "value": "#e9ecef" },
"300": { "value": "#dee2e6" },
"800": { "value": "#343a40" },
"900": { "value": "#212529" }
}
}
}
// tokens/spacing.json
{
"spacing": {
"xs": { "value": "0.25rem" },
"sm": { "value": "0.5rem" },
"md": { "value": "1rem" },
"lg": { "value": "1.5rem" },
"xl": { "value": "2rem" },
"2xl": { "value": "3rem" }
}
}
// Generated output: _tokens.scss
$color-brand-primary: #0066cc;
$color-brand-secondary: #6c757d;
$color-semantic-success: #28a745;
$color-semantic-danger: #dc3545;
$spacing-xs: 0.25rem;
$spacing-sm: 0.5rem;
// ... etc
// Generated output: tokens.css
:root {
--color-brand-primary: #0066cc;
--color-brand-secondary: #6c757d;
--color-semantic-success: #28a745;
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
}
Example: Advanced token transformations
// build.js - Custom Style Dictionary build
const StyleDictionary = require('style-dictionary');
// Custom transform for color opacity
StyleDictionary.registerTransform({
name: 'color/alpha',
type: 'value',
matcher: token => token.attributes.category === 'color',
transformer: token => {
const { value, alpha } = token;
if (alpha) {
// Convert hex to rgba with alpha
const r = parseInt(value.slice(1, 3), 16);
const g = parseInt(value.slice(3, 5), 16);
const b = parseInt(value.slice(5, 7), 16);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
return value;
}
});
// Custom format for SCSS map
StyleDictionary.registerFormat({
name: 'scss/map-nested',
formatter: ({ dictionary }) => {
const tokensMap = dictionary.allTokens.reduce((acc, token) => {
const path = token.path;
let current = acc;
path.forEach((key, index) => {
if (index === path.length - 1) {
current[key] = token.value;
} else {
current[key] = current[key] || {};
current = current[key];
}
});
return acc;
}, {});
const mapToScss = (obj, indent = 1) => {
const spaces = ' '.repeat(indent);
const entries = Object.entries(obj).map(([key, value]) => {
if (typeof value === 'object') {
return `${spaces}'${key}': (\n${mapToScss(value, indent + 1)}\n${spaces})`;
}
return `${spaces}'${key}': ${value}`;
});
return entries.join(',\n');
};
return `$tokens: (\n${mapToScss(tokensMap)}\n);`;
}
});
// Build
const sd = StyleDictionary.extend({
source: ['tokens/**/*.json'],
platforms: {
scss: {
transforms: ['attribute/cti', 'name/cti/kebab', 'color/alpha'],
buildPath: 'src/styles/',
files: [
{
destination: '_tokens.scss',
format: 'scss/variables'
},
{
destination: '_tokens-map.scss',
format: 'scss/map-nested'
}
]
}
}
});
sd.buildAllPlatforms();
// Generated _tokens-map.scss
$tokens: (
'color': (
'brand': (
'primary': #0066cc,
'secondary': #6c757d
),
'semantic': (
'success': #28a745,
'danger': #dc3545
)
),
'spacing': (
'xs': 0.25rem,
'sm': 0.5rem,
'md': 1rem
)
);
// Usage in SCSS
@use 'sass:map';
.button {
background-color: map.get($tokens, 'color', 'brand', 'primary');
padding: map.get($tokens, 'spacing', 'md');
}
Example: Semantic tokens and theming
// tokens/base.json - Base design tokens
{
"color": {
"blue": {
"100": { "value": "#e3f2fd" },
"500": { "value": "#2196f3" },
"900": { "value": "#0d47a1" }
},
"gray": {
"100": { "value": "#f5f5f5" },
"500": { "value": "#9e9e9e" },
"900": { "value": "#212121" }
}
}
}
// tokens/semantic.json - Semantic tokens referencing base
{
"color": {
"text": {
"primary": { "value": "{color.gray.900}" },
"secondary": { "value": "{color.gray.500}" },
"inverse": { "value": "#ffffff" }
},
"background": {
"default": { "value": "#ffffff" },
"subtle": { "value": "{color.gray.100}" }
},
"action": {
"primary": { "value": "{color.blue.500}" },
"primaryHover": { "value": "{color.blue.900}" }
}
}
}
// tokens/dark-theme.json - Dark mode overrides
{
"color": {
"text": {
"primary": { "value": "#ffffff" },
"secondary": { "value": "{color.gray.100}" }
},
"background": {
"default": { "value": "{color.gray.900}" },
"subtle": { "value": "#1a1a1a" }
}
}
}
// build-themes.js
const StyleDictionary = require('style-dictionary');
// Build light theme
const lightTheme = StyleDictionary.extend({
include: ['tokens/base.json'],
source: ['tokens/semantic.json'],
platforms: {
scss: {
transformGroup: 'scss',
buildPath: 'src/styles/themes/',
files: [{
destination: '_light.scss',
format: 'scss/variables',
options: { outputReferences: true }
}]
}
}
});
// Build dark theme
const darkTheme = StyleDictionary.extend({
include: ['tokens/base.json', 'tokens/semantic.json'],
source: ['tokens/dark-theme.json'],
platforms: {
scss: {
transformGroup: 'scss',
buildPath: 'src/styles/themes/',
files: [{
destination: '_dark.scss',
format: 'scss/variables'
}]
}
}
});
lightTheme.buildAllPlatforms();
darkTheme.buildAllPlatforms();
// Usage in SCSS
// main.scss
:root {
@import 'themes/light';
}
[data-theme="dark"] {
@import 'themes/dark';
}
// Or with CSS custom properties
:root {
--color-text-primary: #{$color-text-primary};
--color-background-default: #{$color-background-default};
}
[data-theme="dark"] {
--color-text-primary: #{$color-text-primary-dark};
--color-background-default: #{$color-background-default-dark};
}
Framework Integration Summary
- Bootstrap/Foundation: Override variables before importing, use selective imports for smaller bundles
- CSS-in-JS compatibility: Export SCSS as CSS custom properties or use :export for JS consumption
- Component frameworks: Use CSS Modules (React), scoped styles (Vue), or ViewEncapsulation (Angular)
- SCSS vs Styled Components: SCSS for static/performance, Styled Components for dynamic/type-safe styles
- Design tokens: Use Style Dictionary for platform-agnostic tokens, generate SCSS and CSS variables
- Hybrid approach: Combine SCSS for global/static styles with CSS-in-JS for component dynamics
- Token theming: Build multiple theme outputs from semantic tokens for light/dark modes
- Type safety: Generate TypeScript definitions from design tokens for end-to-end type safety
Note: Modern frameworks support SCSS natively, but choose the right
tool for each use case. Use SCSS for global design systems and CSS-in-JS for component-level dynamic
styling. Design tokens provide the bridge between design tools and code.