Skip to main content

Overview

styled-static provides two APIs for creating variants:
  • styledVariants: Creates a component with variant props
  • cssVariants: Returns a function that generates class strings
Both APIs are fully type-safe and extract CSS at build time.

styledVariants

Create components with type-safe variant props:
import { styled, css, styledVariants } from '@alex.radulescu/styled-static';

const Button = styledVariants({
  component: 'button',
  css: css`
    padding: 0.5rem 1rem;
    border: none;
    border-radius: 4px;
    cursor: pointer;
  `,
  variants: {
    color: {
      primary: css`background: blue; color: white;`,
      danger: css`background: red; color: white;`,
      success: css`background: green; color: white;`,
    },
    size: {
      sm: css`font-size: 0.875rem; padding: 0.25rem 0.5rem;`,
      lg: css`font-size: 1.125rem; padding: 0.75rem 1.5rem;`,
    },
  },
});

// Usage - variant props are type-safe
<Button color="primary" size="lg">Large Primary</Button>
<Button color="danger" size="sm">Small Danger</Button>
Wrap CSS strings in css\…“ to get IDE syntax highlighting from the styled-components VSCode extension.

Default Variants

Specify default values for variants:
const Button = styledVariants({
  component: 'button',
  css: css`padding: 0.5rem 1rem;`,
  variants: {
    color: {
      primary: css`background: blue;`,
      secondary: css`background: gray;`,
    },
    size: {
      sm: css`font-size: 0.875rem;`,
      md: css`font-size: 1rem;`,
      lg: css`font-size: 1.125rem;`,
    },
  },
  defaultVariants: {
    color: 'primary',
    size: 'md',
  },
});

// Uses defaults: color="primary", size="md"
<Button>Click me</Button>

// Override defaults
<Button size="lg">Large Button</Button>

Compound Variants

Apply special styles when multiple variants are combined:
const Button = styledVariants({
  component: 'button',
  css: css`
    padding: 0.5rem 1rem;
    font-size: 1rem;
  `,
  variants: {
    color: {
      primary: css`background: blue;`,
      danger: css`background: red;`,
    },
    size: {
      sm: css`font-size: 0.875rem;`,
      lg: css`font-size: 1.125rem;`,
    },
  },
  compoundVariants: [
    {
      size: 'lg',
      color: 'danger',
      css: css`
        font-weight: 900;
        text-transform: uppercase;
      `,
    },
  ],
});

// Gets compound styles (font-weight: 900, text-transform: uppercase)
<Button size="lg" color="danger">Delete</Button>
Compound variant conditions must match all specified variants to apply.

cssVariants

Generate class strings instead of components:
import { css, cssVariants, cx } from '@alex.radulescu/styled-static';

const badgeCss = cssVariants({
  css: css`
    padding: 0.25rem 0.5rem;
    border-radius: 4px;
    font-size: 0.75rem;
  `,
  variants: {
    variant: {
      info: css`background: #e0f2fe; color: #0369a1;`,
      success: css`background: #dcfce7; color: #166534;`,
      warning: css`background: #fef3c7; color: #92400e;`,
    },
  },
});

// Returns class string
<span className={badgeCss({ variant: 'info' })}>Info</span>
// Returns: "ss-xyz ss-xyz--variant-info"

// Combine with other classes
<span className={cx(badgeCss({ variant: 'success' }), 'ml-2')}>
  Success
</span>

Real-World Example

Here’s the Button component from the styled-static documentation:
import { css, styledVariants } from '@alex.radulescu/styled-static';

export const Button = styledVariants({
  component: 'button',
  css: css`
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 0.5rem 1rem;
    font-size: 0.875rem;
    font-weight: 500;
    font-family: inherit;
    border: none;
    border-radius: 6px;
    cursor: pointer;
    transition: all 0.2s ease;

    &:focus {
      outline: none;
      box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.3);
    }

    &:disabled {
      opacity: 0.5;
      cursor: not-allowed;
    }
  `,
  variants: {
    variant: {
      primary: css`
        background: var(--color-primary);
        color: white;
        &:hover:not(:disabled) {
          background: var(--color-primary-hover);
        }
      `,
      secondary: css`
        background: var(--color-border);
        color: var(--color-text);
        &:hover:not(:disabled) {
          background: var(--color-text-secondary);
          color: white;
        }
      `,
      ghost: css`
        background: transparent;
        color: var(--color-text-secondary);
        &:hover:not(:disabled) {
          background: var(--color-border);
          color: var(--color-text);
        }
      `,
    },
    size: {
      sm: css`
        padding: 0.375rem 0.75rem;
        font-size: 0.8125rem;
      `,
      md: css`
        padding: 0.5rem 1rem;
        font-size: 0.875rem;
      `,
      lg: css`
        padding: 0.75rem 1.5rem;
        font-size: 1rem;
      `,
    },
  },
});

// Usage
<Button variant="primary" size="lg">Large Primary</Button>
<Button variant="ghost" size="sm">Small Ghost</Button>

Variant Class Naming

Variant classes follow a BEM-like pattern:
const Button = styledVariants({
  component: 'button',
  css: css`padding: 1rem;`,
  variants: {
    color: {
      primary: css`background: blue;`,
    },
  },
});

<Button color="primary" />
// Renders: class="ss-abc ss-abc--color-primary"
//                 ↑ base    ↑ variant modifier

Type Safety

Variants are fully type-safe with autocomplete:
const Button = styledVariants({
  component: 'button',
  css: css`padding: 1rem;`,
  variants: {
    color: { primary: css``, danger: css`` },
    size: { sm: css``, lg: css`` },
  },
});

// ✅ Autocomplete for variant names and values
<Button color="primary" size="lg" />

// ❌ TypeScript error: invalid variant value
<Button color="invalid" />

// ❌ TypeScript error: unknown variant name
<Button theme="dark" />

Build-Time Generation

All variant CSS is extracted at build time:
// What you write:
const Button = styledVariants({
  component: 'button',
  css: css`padding: 1rem;`,
  variants: {
    color: {
      blue: css`background: blue;`,
      red: css`background: red;`,
    },
  },
});

// Generated CSS (extracted to static file):
.ss-abc { padding: 1rem; }
.ss-abc--color-blue { background: blue; }
.ss-abc--color-red { background: red; }

// Generated component (runtime):
const Button = Object.assign(
  (props) => {
    // Runtime: map variant props to modifier classes
    const variants = props.color ? `ss-abc--color-${props.color}` : '';
    return createElement('button', {
      ...props,
      className: m(`ss-abc ${variants}`, props.className)
    });
  },
  { className: 'ss-abc' }
);
The runtime only performs string concatenation and sanitization. All CSS generation happens at build time.

Performance

Variants have minimal runtime overhead:
  • CSS is extracted to static files at build time
  • Runtime only does string concatenation
  • Variant values are sanitized to prevent CSS injection
  • No style computation or stylesheet insertion
  • Parse variant definitions
  • Generate CSS for base + all variants
  • Extract to static CSS files
  • Generate type-safe component

Build docs developers (and LLMs) love