Skip to content
Back to skills

Vanilla Extract

ASecurity

Implements zero-runtime CSS using vanilla-extract with type-safe styles, themes, recipes, and sprinkles. Use when wanting type-safe CSS, static extraction at build time, or building design system utilities.

  • 4 stars
  • 0 votes
  • 1 copy
  • 3 views
  • Added September 10, 2026
developmentjavascripttypescriptjavabashreactnextjsnodeapi

Works with

  • cursor
  • cli
  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 3 files and shows the line behind each finding

Scanned September 10, 2026

npx -y skills add mgd34msu/goodvibes-gemini --skill vanilla-extract --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Vanilla Extract?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Vanilla Extract
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/mgd34msu-vanilla-extract/badge)](https://www.skillsdirectory.com/skills/mgd34msu-vanilla-extract)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: vanilla-extract
description: Implements zero-runtime CSS using vanilla-extract with type-safe styles, themes, recipes, and sprinkles. Use when wanting type-safe CSS, static extraction at build time, or building design system utilities.
---

# vanilla-extract

Zero-runtime CSS-in-TypeScript with static extraction at build time.

## Quick Start

**Install:**
```bash
npm install @vanilla-extract/css
# Framework integrations
npm install @vanilla-extract/vite-plugin    # Vite
npm install @vanilla-extract/next-plugin    # Next.js
```

**Configure (Vite):**
```typescript
// vite.config.ts
import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [vanillaExtractPlugin()],
});
```

**Create styles:**
```typescript
// button.css.ts
import { style } from '@vanilla-extract/css';

export const button = style({
  padding: '12px 24px',
  border: 'none',
  borderRadius: 8,
  fontSize: 16,
  cursor: 'pointer',
  backgroundColor: '#3b82f6',
  color: 'white',
  ':hover': {
    backgroundColor: '#2563eb',
  },
});
```

**Use in component:**
```tsx
// Button.tsx
import { button } from './button.css';

export function Button({ children }: { children: React.ReactNode }) {
  return <button className={button}>{children}</button>;
}
```

## Style API

### Basic Styles

```typescript
import { style } from '@vanilla-extract/css';

export const container = style({
  maxWidth: 1200,
  margin: '0 auto',
  padding: 16,
});

// Numbers become pixels (except unitless properties)
export const box = style({
  padding: 16,        // 16px
  margin: 8,          // 8px
  opacity: 0.5,       // unitless
  flexGrow: 1,        // unitless
  lineHeight: 1.5,    // unitless
});
```

### Pseudo-Selectors

```typescript
export const link = style({
  color: '#3b82f6',
  textDecoration: 'none',

  ':hover': {
    textDecoration: 'underline',
  },

  ':focus-visible': {
    outline: '2px solid #3b82f6',
    outlineOffset: 2,
  },

  '::before': {
    content: '">"',
    marginRight: 4,
  },
});
```

### Complex Selectors

```typescript
export const card = style({
  padding: 16,

  selectors: {
    // Target self with conditions
    '&:first-child': {
      marginTop: 0,
    },

    // Adjacent sibling
    '& + &': {
      marginTop: 16,
    },

    // Parent hover (& must appear in selector)
    '.dark-mode &': {
      backgroundColor: '#1f2937',
    },

    // Direct child - use globalStyle instead
    // '& > div': { } // Invalid!
  },
});
```

### Media Queries

```typescript
export const responsiveBox = style({
  padding: 16,

  '@media': {
    '(min-width: 768px)': {
      padding: 24,
    },
    '(min-width: 1024px)': {
      padding: 32,
    },
    '(prefers-color-scheme: dark)': {
      backgroundColor: '#1f2937',
    },
  },
});
```

### Container Queries

```typescript
export const containerParent = style({
  containerType: 'inline-size',
});

export const responsiveChild = style({
  padding: 16,

  '@container': {
    '(min-width: 400px)': {
      padding: 24,
    },
  },
});
```

## CSS Variables

### createVar

```typescript
import { style, createVar } from '@vanilla-extract/css';

const accentColor = createVar();
const spacing = createVar();

export const container = style({
  vars: {
    [accentColor]: '#3b82f6',
    [spacing]: '16px',
  },
  padding: spacing,
  borderColor: accentColor,
});

export const altContainer = style({
  vars: {
    [accentColor]: '#10b981', // Override
  },
});
```

### Fallback Values

```typescript
import { fallbackVar } from '@vanilla-extract/css';

export const box = style({
  color: fallbackVar(accentColor, 'blue'),
});
```

## Style Variants

```typescript
import { styleVariants } from '@vanilla-extract/css';

// Simple variants
export const color = styleVariants({
  primary: { backgroundColor: '#3b82f6', color: 'white' },
  secondary: { backgroundColor: '#e5e7eb', color: '#1f2937' },
  danger: { backgroundColor: '#ef4444', color: 'white' },
});

// With composition
const base = style({
  padding: '12px 24px',
  borderRadius: 8,
  border: 'none',
});

export const button = styleVariants({
  primary: [base, { backgroundColor: '#3b82f6', color: 'white' }],
  secondary: [base, { backgroundColor: '#e5e7eb', color: '#1f2937' }],
});

// Usage
<button className={button.primary}>Primary</button>
<button className={color['secondary']}>Secondary</button>
```

## Global Styles

```typescript
import { globalStyle, style } from '@vanilla-extract/css';

// Global reset
globalStyle('*, *::before, *::after', {
  boxSizing: 'border-box',
});

globalStyle('body', {
  margin: 0,
  fontFamily: 'system-ui, sans-serif',
});

// Reference scoped classes
const card = style({ padding: 16 });

globalStyle(`${card} > h2`, {
  margin: 0,
  fontSize: 24,
});

globalStyle(`${card} p`, {
  color: '#6b7280',
});
```

## Theming

### Create Theme

```typescript
// theme.css.ts
import { createTheme } from '@vanilla-extract/css';

export const [themeClass, vars] = createTheme({
  colors: {
    primary: '#3b82f6',
    secondary: '#6b7280',
    background: '#ffffff',
    text: '#1f2937',
  },
  spacing: {
    sm: '8px',
    md: '16px',
    lg: '24px',
  },
  borderRadius: {
    sm: '4px',
    md: '8px',
    lg: '16px',
  },
});
```

### Use Theme Variables

```typescript
// button.css.ts
import { style } from '@vanilla-extract/css';
import { vars } from './theme.css';

export const button = style({
  padding: vars.spacing.md,
  borderRadius: vars.borderRadius.md,
  backgroundColor: vars.colors.primary,
  color: '#fff',
});
```

### Multiple Themes

```typescript
import { createTheme, createThemeContract } from '@vanilla-extract/css';

// Define contract (structure only)
const themeContract = createThemeContract({
  colors: {
    background: null,
    text: null,
    primary: null,
  },
});

// Light theme
export const lightTheme = createTheme(themeContract, {
  colors: {
    background: '#ffffff',
    text: '#1f2937',
    primary: '#3b82f6',
  },
});

// Dark theme
export const darkTheme = createTheme(themeContract, {
  colors: {
    background: '#1f2937',
    text: '#f9fafb',
    primary: '#60a5fa',
  },
});

export { themeContract as vars };
```

**Apply theme:**
```tsx
function App() {
  const [isDark, setIsDark] = useState(false);

  return (
    <div className={isDark ? darkTheme : lightTheme}>
      <button onClick={() => setIsDark(!isDark)}>Toggle</button>
    </div>
  );
}
```

## Recipes

Multi-variant component styles.

**Install:**
```bash
npm install @vanilla-extract/recipes
```

```typescript
// button.css.ts
import { recipe, RecipeVariants } from '@vanilla-extract/recipes';

export const button = recipe({
  base: {
    display: 'inline-flex',
    alignItems: 'center',
    justifyContent: 'center',
    border: 'none',
    borderRadius: 8,
    cursor: 'pointer',
    fontWeight: 600,
  },

  variants: {
    color: {
      primary: {
        backgroundColor: '#3b82f6',
        color: 'white',
      },
      secondary: {
        backgroundColor: '#e5e7eb',
        color: '#1f2937',
      },
      danger: {
        backgroundColor: '#ef4444',
        color: 'white',
      },
    },
    size: {
      sm: { padding: '8px 16px', fontSize: 14 },
      md: { padding: '12px 24px', fontSize: 16 },
      lg: { padding: '16px 32px', fontSize: 18 },
    },
  },

  compoundVariants: [
    {
      variants: { color: 'primary', size: 'lg' },
      style: {
        boxShadow: '0 4px 12px rgba(59, 130, 246, 0.4)',
      },
    },
  ],

  defaultVariants: {
    color: 'primary',
    size: 'md',
  },
});

// Type extraction
export type ButtonVariants = RecipeVariants<typeof button>;
```

**Usage:**
```tsx
import { button, ButtonVariants } from './button.css';

interface ButtonProps extends ButtonVariants {
  children: React.ReactNode;
}

export function Button({ color, size, children }: ButtonProps) {
  return (
    <button className={button({ color, size })}>
      {children}
    </button>
  );
}

// Usage
<Button color="primary" size="lg">Click me</Button>
<Button color="danger">Delete</Button>
```

## Sprinkles

Build atomic CSS utilities.

**Install:**
```bash
npm install @vanilla-extract/sprinkles
```

```typescript
// sprinkles.css.ts
import { defineProperties, createSprinkles } from '@vanilla-extract/sprinkles';

const space = {
  none: '0',
  sm: '4px',
  md: '8px',
  lg: '16px',
  xl: '24px',
};

const colors = {
  primary: '#3b82f6',
  secondary: '#6b7280',
  white: '#ffffff',
  black: '#000000',
};

const responsiveProperties = defineProperties({
  conditions: {
    mobile: {},
    tablet: { '@media': '(min-width: 768px)' },
    desktop: { '@media': '(min-width: 1024px)' },
  },
  defaultCondition: 'mobile',
  properties: {
    display: ['none', 'flex', 'block', 'grid'],
    flexDirection: ['row', 'column'],
    alignItems: ['stretch', 'center', 'flex-start', 'flex-end'],
    justifyContent: ['stretch', 'center', 'flex-start', 'flex-end', 'space-between'],
    gap: space,
    padding: space,
    paddingTop: space,
    paddingBottom: space,
    paddingLeft: space,
    paddingRight: space,
    margin: space,
  },
  shorthands: {
    p: ['padding'],
    px: ['paddingLeft', 'paddingRight'],
    py: ['paddingTop', 'paddingBottom'],
    m: ['margin'],
  },
});

const colorProperties = defineProperties({
  properties: {
    color: colors,
    backgroundColor: colors,
  },
});

export const sprinkles = createSprinkles(
  responsiveProperties,
  colorProperties
);

export type Sprinkles = Parameters<typeof sprinkles>[0];
```

**Usage:**
```tsx
import { sprinkles } from './sprinkles.css';

function Box() {
  return (
    <div className={sprinkles({
      display: 'flex',
      gap: 'lg',
      p: { mobile: 'md', desktop: 'xl' },
      backgroundColor: 'white',
    })}>
      Content
    </div>
  );
}
```

## Framework Setup

### Next.js

```javascript
// next.config.js
const { createVanillaExtractPlugin } = require('@vanilla-extract/next-plugin');
const withVanillaExtract = createVanillaExtractPlugin();

module.exports = withVanillaExtract({
  // Next.js config
});
```

### Vite

```typescript
// vite.config.ts
import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin';

export default defineConfig({
  plugins: [vanillaExtractPlugin()],
});
```

## Best Practices

1. **Use `.css.ts` extension** - Required for processing
2. **Colocate styles** - Keep near components
3. **Export vars** - Share theme variables
4. **Use recipes for variants** - Type-safe component APIs
5. **Sprinkles for utilities** - Build design system primitives

## Reference Files

- [references/recipes.md](references/recipes.md) - Recipe patterns
- [references/sprinkles.md](references/sprinkles.md) - Atomic CSS utilities

Files in this skill

  • SKILL.md10.5 KB
  • references/recipes.md7.6 KB
  • references/sprinkles.md9.1 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.