Create a Design System in Under 15 Minutes

Build a complete, production-ready design system with Styleframe using composable design tokens. Follow this step-by-step guide to create colors, fluid typography, spacing, and more in just 15 minutes.

Building a design system from scratch can be daunting. Between defining color palettes, typography scales, spacing systems, and component styles, it often takes weeks to get everything right.

Introducing styleframe. In this guide, you'll build a complete, production-ready design system in under 15 minutes using styleframe's composable design tokens with fluid responsive typography.

What you'll build: A cohesive design system with a full color palette (including automatic variants, tints, and shades), fluid typography that scales smoothly across viewports, consistent spacing, border tokens, shadows, and responsive breakpoints—all type-safe and theme-ready.

Prerequisites

Before starting, make sure you have:

  • Node.js 18+ installed
  • A project with Styleframe and Styleframe Pro installed (Installation Guide)
  • Basic understanding of CSS and TypeScript

Step 1: Foundation – Colors (2 minutes)

Every great design system starts with colors. Let's create a comprehensive color system with automatic variants using the OKLCH color space.

Define Your Base Colors

Open your styleframe.config.ts and define your brand colors:

import { styleframe } from 'styleframe';
import { useColor, useColorLightness, useColorShade, useColorTint } from '@styleframe/theme';

const s = styleframe();

// Define your brand colors
const { colorPrimary, colorSecondary, colorGray } = useColor(s, {
    primary: '#0066ff',
    secondary: '#7c3aed',
    gray: '#64748b',
} as const);

// Define semantic colors
const { colorSuccess, colorWarning, colorDanger, colorInfo } = useColor(s, {
    success: '#10b981',
    warning: '#f59e0b',
    danger: '#ef4444',
    info: '#06b6d4',
} as const);

// Generate colors - Primary lightness variants
const {
    colorPrimary50,
    colorPrimary100,
    colorPrimary200,
    colorPrimary300,
    colorPrimary400,
    colorPrimary500,
    colorPrimary600,
    colorPrimary700,
    colorPrimary800,
    colorPrimary900,
    colorPrimary950,
} = useColorLightness(s, colorPrimary);

// Generate colors - Gray lightness variants
const {
    colorGray50,
    colorGray100,
    colorGray200,
    colorGray300,
    colorGray400,
    colorGray500,
    colorGray600,
    colorGray700,
    colorGray800,
    colorGray900,
    colorGray950,
} = useColorLightness(s, colorGray);

// Generate shades (darker variants) for hover/active states
const {
    colorPrimaryShade50,    // 5% darker
    colorPrimaryShade100,   // 10% darker
    colorPrimaryShade150,   // 15% darker
    colorPrimaryShade200,   // 20% darker
} = useColorShade(s, colorPrimary);

// Generate tints (lighter variants) for subtle backgrounds
const {
    colorPrimaryTint50,     // 5% lighter
    colorPrimaryTint100,    // 10% lighter
    colorPrimaryTint150,    // 15% lighter
    colorPrimaryTint200,    // 20% lighter
} = useColorTint(s, colorPrimary);

export default s;

That's it! You now have a complete color system with:

  • 11 lightness variants per color (50-950)
  • 4 shade variants (darker) for hover/active states
  • 4 tint variants (lighter) for subtle backgrounds

All perceptually uniform thanks to OKLCH. No manual color picking required!

Use these for:

  • Shades: Hover/active states on buttons and interactive elements
  • Tints: Subtle backgrounds, badges, alerts, and highlighted areas
Why OKLCH? Unlike HSL, OKLCH maintains perceptual uniformity — meaning lightness 50% actually looks halfway between black and white across all hues. This ensures your color scales look consistent.Learn more about colors →

Step 2: Scales – The Secret Sauce (1 minute)

Modular scales are the foundation of harmonious design systems. They use mathematical ratios to create proportional relationships between sizes.

For fluid typography, we'll define separate scales for mobile and desktop to optimize readability at different viewport sizes:

import { useScale, useScalePowers, defaultScaleValues } from '@styleframe/theme';

// Use Minor Third (1.2) for mobile, Major Third (1.25) for desktop
const { scaleMin, scaleMax } = useScale(s, {
    ...defaultScaleValues,
    min: '@minor-third',   // 1.2 - subtle scaling for small screens
    max: '@major-third',   // 1.25 - stronger hierarchy for large screens
});

// Generate scale powers from -3 to 5
// These will be multipliers for creating proportional sizes
const scaleMinPowers = useScalePowers(s, scaleMin, [-3, -2, -1, 0, 1, 2, 3, 4, 5]);
const scaleMaxPowers = useScalePowers(s, scaleMax, [-3, -2, -1, 0, 1, 2, 3, 4, 5]);

These scale powers will be used to create mathematically harmonious typography and spacing systems that adapt to viewport size.

Scale Selection for Fluid Typography:
  • Minor Third → Major Third (1.2 → 1.25): Balanced, works for most sites
  • Minor Third → Perfect Fourth (1.2 → 1.333): More dramatic hierarchy for desktop
  • Major Second → Minor Third (1.125 → 1.2): Subtle, great for dense content
  • Major Third → Perfect Fourth (1.25 → 1.333): Bold, ideal for marketing sites
Learn more about scales →

Step 3: Fluid Typography System (3 minutes)

With your scales defined, let's create a fluid typography system that scales smoothly across all viewport sizes — no breakpoints needed!

Setup Fluid Viewport

First, establish the viewport range for fluid calculations:

import { useFluidViewport } from '@styleframe/pro';

// Set up fluid viewport (320px mobile → 1440px desktop)
useFluidViewport(s);

This creates the foundation for all fluid design tokens. The composable automatically calculates a fluid breakpoint variable that represents the viewport's position between min and max widths.

Learn more about fluid viewports →

Fluid Font Sizes with Modular Scales

Create font sizes that scale smoothly from mobile to desktop using your modular scales:

import { useFluidFontSize } from '@styleframe/pro';

// Generate fluid font sizes that scale from 16px→18px at the base
const {
    fontSize,
    fontSizeXs,    // Scale power -2: scales smoothly between mobile and desktop
    fontSizeSm,    // Scale power -1
    fontSizeMd,    // Scale power 0 (base size)
    fontSizeLg,    // Scale power 1
    fontSizeXl,    // Scale power 2
    fontSize2xl,   // Scale power 3
    fontSize3xl,   // Scale power 4
    fontSize4xl,   // Scale power 5
} = useFluidFontSize(
    s,
    { min: 16, max: 18 },  // Base font size: 16px mobile → 18px desktop
    {
        xs: { min: scaleMinPowers[-2], max: scaleMaxPowers[-2] },
        sm: { min: scaleMinPowers[-1], max: scaleMaxPowers[-1] },
        md: { min: scaleMinPowers[0], max: scaleMaxPowers[0] },
        lg: { min: scaleMinPowers[1], max: scaleMaxPowers[1] },
        xl: { min: scaleMinPowers[2], max: scaleMaxPowers[2] },
        '2xl': { min: scaleMinPowers[3], max: scaleMaxPowers[3] },
        '3xl': { min: scaleMinPowers[4], max: scaleMaxPowers[4] },
        '4xl': { min: scaleMinPowers[5], max: scaleMaxPowers[5] },
        default: '@md',
    }
);

The magic: Each font size automatically scales between mobile and desktop using complex CSS calc() functions. Your h1 might go from 38px on mobile to 90px on desktop—smoothly scaling at every viewport width in between!

Learn more about fluid typography →

Font Families

Define your font stacks:

import { useFontFamily, useFontWeight } from '@styleframe/theme';

const { fontFamily, fontFamilyMono, fontFamilyDisplay } = useFontFamily(s, {
    default: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
    mono: '"SF Mono", Monaco, "Cascadia Code", "Roboto Mono", monospace',
    display: '"Inter Display", -apple-system, sans-serif',
} as const);

const { 
    fontWeightNormal, 
    fontWeightMedium, 
    fontWeightSemibold, 
    fontWeightBold 
} = useFontWeight(s);

Line Heights and Letter Spacing

Complete your typography system with optimal readability:

import { useLineHeight, useLetterSpacing } from '@styleframe/theme';

const {
    lineHeightTight,    // 1.25 - for headlines
    lineHeightNormal,   // 1.5 - for body text
    lineHeightRelaxed,  // 1.75 - for long-form content
} = useLineHeight(s);

const {
    letterSpacingTight,   // -0.025em - for large text
    letterSpacingNormal,  // 0 - standard
    letterSpacingWide,    // 0.025em - for small text/labels
} = useLetterSpacing(s);

Learn more about typography →

Step 4: Spacing System (1 minute)

Create consistent spacing using the same scale for visual harmony:

import { useSpacing, useMultiplier } from '@styleframe/theme';

// Define base spacing
const { spacing } = useSpacing(s, { default: '1rem' } as const);

// Generate spacing scale using the mobile scale for consistency
const {
    spacing3xs,  // ~0.579rem
    spacing2xs,  // ~0.694rem
    spacingXs,   // ~0.833rem
    spacingSm,   // ~0.833rem
    spacingMd,   // 1rem (base)
    spacingLg,   // ~1.2rem
    spacingXl,   // ~1.44rem
    spacing2xl,  // ~1.728rem
    spacing3xl,  // ~2.074rem
} = useMultiplier(s, spacing, {
    '3xs': scaleMinPowers[-3],
    '2xs': scaleMinPowers[-2],
    xs: scaleMinPowers[-1],
    sm: scaleMinPowers[-1],
    md: scaleMinPowers[0],
    lg: scaleMinPowers[1],
    xl: scaleMinPowers[2],
    '2xl': scaleMinPowers[3],
    '3xl': scaleMinPowers[4],
});
Pro tip: Using the same modular scale for spacing creates mathematical harmony with your typography. Everything feels proportionally connected!Learn more about spacing →

Step 5: Visual Depth (2 minutes)

Add borders, shadows, and breakpoints to complete your system.

Borders

import { useBorderWidth, useBorderStyle, useBorderColor } from '@styleframe/theme';

const { 
    borderWidth,
    borderWidthThin,
    borderWidthMedium,
    borderWidthThick,
} = useBorderWidth(s);

const { borderStyle } = useBorderStyle(s);

// Use your gray scale for border colors
const { 
    borderColor,
    borderColorLight,
    borderColorDark,
} = useBorderColor(s, {
    default: s.ref(colorGray300),
    light: s.ref(colorGray200),
    dark: s.ref(colorGray400),
} as const);

Learn more about borders →

Border Radiuses

Create consistent rounded corners using scale-based values:

import { useBorderRadius, useMultiplier } from '@styleframe/theme';

// Define base border radius
const { 
    borderRadius,
    borderRadiusFull,
} = useBorderRadius(s, { 
    default: '0.25rem',
    full: '9999px', // For pills/circles
});

// Generate border radius scale using your modular scale
const {
    borderRadiusXs,
    borderRadiusSm,
    borderRadiusMd,
    borderRadiusLg,
    borderRadiusXl,
    borderRadius2xl,
} = useMultiplier(s, borderRadius, {
    xs: scaleMinPowers[-2],   // Subtle rounding
    sm: scaleMinPowers[-1],   // Small rounding
    md: scaleMinPowers[0],    // Base rounding
    lg: scaleMinPowers[1],    // Large rounding
    xl: scaleMinPowers[2],    // Extra large rounding
    '2xl': scaleMinPowers[3], // Maximum rounding
});

// Or create special purpose values
const { borderRadiusFull } = useBorderRadius(s, { full: '9999px' }); // For pills/circles

Learn more about border radiuses →

Box Shadows

Create an elevation system:

import { useBoxShadow } from '@styleframe/theme';

const {
    boxShadowSm,      // Subtle elevation
    boxShadow,        // Standard elevation
    boxShadowMd,      // Medium elevation
    boxShadowLg,      // High elevation
    boxShadowXl,      // Maximum elevation
} = useBoxShadow(s);

Learn more about box shadows →

Responsive Breakpoints

Even with fluid typography, breakpoints are useful for layout changes:

import { useBreakpoint } from '@styleframe/theme';

const {
    breakpointXs,   // 0px - mobile
    breakpointSm,   // 576px - tablets
    breakpointMd,   // 992px - laptops
    breakpointLg,   // 1200px - desktops
    breakpointXl,   // 1440px - large screens
} = useBreakpoint(s);

Learn more about breakpoints →

Step 6: Put It All Together

Here's your complete fluid design system in one place:

import { styleframe } from 'styleframe';
import {
    useColor,
    useColorLightness,
    useColorShade,
    useColorTint,
    useScale,
    useScalePowers,
    useFontFamily,
    useFontWeight,
    useLineHeight,
    useLetterSpacing,
    useSpacing,
    useBorderWidth,
    useBorderStyle,
    useBorderColor,
    useBorderRadius,
    useBoxShadow,
    useBreakpoint,
    useMultiplier,
    defaultScaleValues,
} from '@styleframe/theme';
import { useFluidViewport, useFluidFontSize } from '@styleframe/pro';

const s = styleframe();

// 1. Colors
const { colorPrimary, colorSecondary, colorGray } = useColor(s, {
    primary: '#0066ff',
    secondary: '#7c3aed',
    gray: '#64748b',
} as const);

const { colorSuccess, colorWarning, colorDanger, colorInfo } = useColor(s, {
    success: '#10b981',
    warning: '#f59e0b',
    danger: '#ef4444',
    info: '#06b6d4',
} as const);

// Generate color variants
// Generate lightness variants for primary color
const {
    colorPrimary50,
    colorPrimary100,
    colorPrimary200,
    colorPrimary300,
    colorPrimary400,
    colorPrimary500,
    colorPrimary600,
    colorPrimary700,
    colorPrimary800,
    colorPrimary900,
    colorPrimary950,
} = useColorLightness(s, colorPrimary);

// Generate lightness variants for gray (for UI backgrounds, borders, text)
const {
    colorGray50,
    colorGray100,
    colorGray200,
    colorGray300,
    colorGray400,
    colorGray500,
    colorGray600,
    colorGray700,
    colorGray800,
    colorGray900,
    colorGray950,
} = useColorLightness(s, colorGray);

// Generate shades and tints for interactive states
const {
    colorPrimaryShade50,
    colorPrimaryShade100,
    colorPrimaryShade150,
    colorPrimaryShade200,
} = useColorShade(s, colorPrimary);

const {
    colorPrimaryTint50,
    colorPrimaryTint100,
    colorPrimaryTint150,
    colorPrimaryTint200,
} = useColorTint(s, colorPrimary);

// 2. Scales for Fluid Typography
const { scaleMin, scaleMax } = useScale(s, {
    ...defaultScaleValues,
    min: '@minor-third',   // 1.2 for mobile
    max: '@major-third',   // 1.25 for desktop
});

const scaleMinPowers = useScalePowers(s, scaleMin, [-3, -2, -1, 0, 1, 2, 3, 4, 5]);
const scaleMaxPowers = useScalePowers(s, scaleMax, [-3, -2, -1, 0, 1, 2, 3, 4, 5]);

// 3. Fluid Typography
useFluidViewport(s);

const { fontFamily, fontFamilyMono, fontFamilyDisplay } = useFontFamily(s, {
    default: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
    mono: '"SF Mono", Monaco, "Cascadia Code", "Roboto Mono", monospace',
    display: '"Inter Display", -apple-system, sans-serif',
} as const);

const fontSizes = useFluidFontSize(
    s,
    { min: 16, max: 18 },
    {
        xs: { min: scaleMinPowers[-2], max: scaleMaxPowers[-2] },
        sm: { min: scaleMinPowers[-1], max: scaleMaxPowers[-1] },
        md: { min: scaleMinPowers[0], max: scaleMaxPowers[0] },
        lg: { min: scaleMinPowers[1], max: scaleMaxPowers[1] },
        xl: { min: scaleMinPowers[2], max: scaleMaxPowers[2] },
        '2xl': { min: scaleMinPowers[3], max: scaleMaxPowers[3] },
        '3xl': { min: scaleMinPowers[4], max: scaleMaxPowers[4] },
        '4xl': { min: scaleMinPowers[5], max: scaleMaxPowers[5] },
        default: '@md',
    }
);

const { fontWeight, fontWeightNormal, fontWeightMedium, fontWeightSemibold, fontWeightBold } = useFontWeight(s);
const { lineHeight, lineHeightTight, lineHeightNormal, lineHeightRelaxed } = useLineHeight(s);
const { letterSpacing, letterSpacingTight, letterSpacingNormal, letterSpacingWide } = useLetterSpacing(s);

// 4. Spacing
const { spacing } = useSpacing(s, { default: '1rem' } as const);
const spacings = useMultiplier(s, spacing, {
    '3xs': scaleMinPowers[-3],
    '2xs': scaleMinPowers[-2],
    xs: scaleMinPowers[-1],
    sm: scaleMinPowers[-1],
    md: scaleMinPowers[0],
    lg: scaleMinPowers[1],
    xl: scaleMinPowers[2],
    '2xl': scaleMinPowers[3],
    '3xl': scaleMinPowers[4],
});

// 5. Visual Depth

const { borderStyle } = useBorderStyle(s);

const { borderWidth, borderWidthThin, borderWidthMedium, borderWidthThick } = useBorderWidth(s);

const { borderColor, borderLight, borderColorDark } = useBorderColor(s, {
    default: s.ref(colorGray300),
    light: s.ref(colorGray200),
    dark: s.ref(colorGray400),
} as const);

const { 
    borderRadius,
    borderRadiusFull, 
} = useBorderRadius(s, { 
    default: '0.25rem',
    full: '9999px',
});
const { borderRadiusXs, borderRadiusSm, borderRadiusMd, borderRadiusLg, borderRadiusXl, borderRadius2xl } = useMultiplier(s, borderRadius, {
    xs: scaleMinPowers[-2],
    sm: scaleMinPowers[-1],
    md: scaleMinPowers[0],
    lg: scaleMinPowers[1],
    xl: scaleMinPowers[2],
    '2xl': scaleMinPowers[3],
});

const {  boxShadow, boxShadowSm, boxShadowMd, boxShadowLg, boxShadowXl } = useBoxShadow(s);

const { breakpointSm, breakpointMd, breakpointLg, breakpointXl } = useBreakpoint(s);

export default s;

Next Steps: Components & Recipes

Your design system tokens are now complete! With these tokens in place, you're ready to build:

Components (Coming Soon)

Pre-built, customizable UI components that use your design tokens:

  • Buttons (primary, secondary, outline, ghost variants)
  • Forms (inputs, textareas, selects, checkboxes)
  • Cards (with headers, footers, images)
  • Modals & Dialogs
  • Navigation (navbar, sidebar, breadcrumbs)
  • Alerts & Notifications

Stay tuned for component documentation!

Utilities (Coming Soon)

Helper classes and functions to speed up development:

  • Flexbox & Grid utilities
  • Text alignment & decoration
  • Margin & padding shortcuts
  • Display & visibility helpers
  • Positioning utilities (absolute, relative, fixed)

Learn more about utilities →

Recipes (Coming Soon)

Recipes are advanced component styling systems that combine utility declarations with configurable variants. They provide a powerful way to create flexible, reusable UI components with type-safe variant selection.

Recipes support:

  • Default variants: Specify which variant applies when none is chosen
  • Compound variants: Define special styling for specific variant combinations
  • Full type safety: TypeScript support for variant names and values
  • Runtime flexibility: Generate utility class strings on the fly

Learn more about recipes →

Examples

Using Your Design Tokens

Now that you've created your design system, let's use it to build some components!

Example: Button Component

const { ref, selector, css } = s;

selector('.btn', {
    fontFamily: ref(fontFamily),
    fontSize: ref(fontSize),
    fontWeight: ref(fontWeight),
    lineHeight: ref(lineHeight),
    
    paddingTop: ref(spacingSm),
    paddingBottom: ref(spacingSm),
    paddingLeft: ref(spacingMd),
    paddingRight: ref(spacingMd),
    
    borderRadius: ref(borderRadius),
    borderWidth: ref(borderWidth),
    borderStyle: ref(borderStyle),
    borderColor: ref(borderColor),
    backgroundColor: ref(colorPrimary),
    color: 'white',
    
    cursor: 'pointer',
    transition: 'all 0.2s ease',
});

selector('.btn:hover', {
    backgroundColor: ref(colorPrimaryShade50),
    boxShadow: ref(boxShadow),
});

selector('.btn:active', {
    backgroundColor: ref(colorPrimaryShade100),
    boxShadow: 'none',
});

// Subtle variant using tints
selector('.btn-subtle', {
    fontFamily: ref(fontFamily),
    fontSize: ref(fontSize),
    fontWeight: ref(fontWeight),
    lineHeight: ref(lineHeight),
    
    paddingTop: ref(spacingSm),
    paddingBottom: ref(spacingSm),
    paddingLeft: ref(spacingMd),
    paddingRight: ref(spacingMd),
    
    borderRadius: ref(borderRadiusMd),
    borderWidth: ref(borderWidth),
    borderStyle: ref(borderStyle),
    borderColor: ref(colorPrimary),
    backgroundColor: ref(colorPrimaryTint150),
    color: ref(colorPrimary),
    
    cursor: 'pointer',
    transition: 'all 0.2s ease',
});

selector('.btn-subtle:hover', {
    backgroundColor: ref(colorPrimaryTint100),
});

What Makes This Different?

Traditional design systems require:

  • ❌ Manually defining dozens of color shades
  • ❌ Creating hover and active state colors by hand
  • ❌ Creating multiple media queries for responsive typography
  • ❌ Picking arbitrary font sizes at each breakpoint
  • ❌ Inconsistent spacing values across your app
  • ❌ Brittle, hard-coded CSS values
  • ❌ Weeks of refinement to get proportions right

With Styleframe's fluid design system, you get:

  • Automatic color variants using perceptually uniform OKLCH
  • Automatic tints and shades for interactive states
  • Smooth responsive scaling without breakpoints
  • Mathematical harmony through modular scales
  • Type-safe tokens with full autocomplete
  • Theme-ready from day one
  • Production-ready in under 15 minutes

Key Takeaways

  1. Start with colors: Define your brand colors and generate variants, tints, and shades automatically
  2. Choose mobile and desktop scales: Select modular scale ratios for each viewport size
  3. Enable fluid viewport: Set up the foundation for fluid design tokens
  4. Create fluid typography: Font sizes scale smoothly across all screen sizes
  5. Build systematic spacing: Use the same scale for visual harmony
  6. Add depth: Borders, shadows, and breakpoints complete the system
  7. Stay flexible: All tokens can be overridden for themes or special cases

Troubleshooting

Additional Resources


Congratulations! 🎉 You've built a production-ready fluid design system in under 15 minutes. Your design tokens are type-safe, theme-ready, mathematically harmonious, and scale beautifully across all viewport sizes. Now go build something beautiful!