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.
Implements zero-runtime CSS-in-TypeScript with static extraction at build time. Use when creating type-safe styles, themes, recipes, or atomic utilities that need to be extracted at build time rather than runtime.
/plugin marketplace add mgd34msu/goodvibes-plugin/plugin install goodvibes@goodvibes-marketThis skill inherits all available tools. When active, it can use any tool Claude has access to.
references/recipes.mdreferences/sprinkles.mdZero-runtime CSS-in-TypeScript with static extraction at build time.
Install:
npm install @vanilla-extract/css
# Framework integrations
npm install @vanilla-extract/vite-plugin # Vite
npm install @vanilla-extract/next-plugin # Next.js
Configure (Vite):
// vite.config.ts
import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [vanillaExtractPlugin()],
});
Create styles:
// 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:
// Button.tsx
import { button } from './button.css';
export function Button({ children }: { children: React.ReactNode }) {
return <button className={button}>{children}</button>;
}
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
});
export const link = style({
color: '#3b82f6',
textDecoration: 'none',
':hover': {
textDecoration: 'underline',
},
':focus-visible': {
outline: '2px solid #3b82f6',
outlineOffset: 2,
},
'::before': {
content: '">"',
marginRight: 4,
},
});
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!
},
});
export const responsiveBox = style({
padding: 16,
'@media': {
'(min-width: 768px)': {
padding: 24,
},
'(min-width: 1024px)': {
padding: 32,
},
'(prefers-color-scheme: dark)': {
backgroundColor: '#1f2937',
},
},
});
export const containerParent = style({
containerType: 'inline-size',
});
export const responsiveChild = style({
padding: 16,
'@container': {
'(min-width: 400px)': {
padding: 24,
},
},
});
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
},
});
import { fallbackVar } from '@vanilla-extract/css';
export const box = style({
color: fallbackVar(accentColor, 'blue'),
});
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>
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',
});
// 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',
},
});
// 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',
});
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:
function App() {
const [isDark, setIsDark] = useState(false);
return (
<div className={isDark ? darkTheme : lightTheme}>
<button onClick={() => setIsDark(!isDark)}>Toggle</button>
</div>
);
}
Multi-variant component styles.
Install:
npm install @vanilla-extract/recipes
// 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:
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>
Build atomic CSS utilities.
Install:
npm install @vanilla-extract/sprinkles
// 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:
import { sprinkles } from './sprinkles.css';
function Box() {
return (
<div className={sprinkles({
display: 'flex',
gap: 'lg',
p: { mobile: 'md', desktop: 'xl' },
backgroundColor: 'white',
})}>
Content
</div>
);
}
// next.config.js
const { createVanillaExtractPlugin } = require('@vanilla-extract/next-plugin');
const withVanillaExtract = createVanillaExtractPlugin();
module.exports = withVanillaExtract({
// Next.js config
});
// vite.config.ts
import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin';
export default defineConfig({
plugins: [vanillaExtractPlugin()],
});
.css.ts extension - Required for processingThis skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.