AgentSkillsCN

Styling

样式设计

SKILL.md

Comprehensive Styling Approaches for Next.js

Comprehensive styling approaches for Next.js. Covers design tokens (JSON, Style Dictionary, CSS variables), CSS Modules, CSS-in-JS solutions (styled-components, vanilla-extract, Panda CSS), global styles, and when to use which approach.

License: MIT Author: Bala Version: 0.1.0

Overview: Styling Options in Next.js

Next.js provides multiple ways to style your application. Choose based on your project needs:

ApproachUse CaseBundle SizeServer Components
Tailwind CSSRapid prototyping, utility-firstSmallestFull support
CSS ModulesComponent-scoped stylesSmallFull support
vanilla-extractType-safe, design systemsZero-runtimeFull support
Panda CSSBuild-time CSS-in-JSSmallFull support
styled-componentsDynamic theming, runtime CSSMediumLimited ("use client")
CSS variablesToken-based themingMinimalFull support

Tailwind CSS (Recommended for Most Projects)

See tailwindcss skill file for complete Tailwind documentation.

Best for:

  • Rapid development
  • Design consistency
  • Responsive design
  • Dark mode
  • Teams familiar with utility-first CSS

Setup:

bash
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

CSS Modules

Built-in support with Next.js. Perfect for scoped, component-level styling.

Basic Usage

css
/* Button.module.css */
.button {
  padding: 8px 16px;
  border-radius: 4px;
  background-color: #3b82f6;
  color: white;
  border: none;
  cursor: pointer;
  font-weight: 500;
}

.button:hover {
  background-color: #2563eb;
}

.primary {
  composes: button;
  background-color: #3b82f6;
}

.secondary {
  composes: button;
  background-color: #6b7280;
}
typescript
// Button.tsx
import styles from './Button.module.css'

interface ButtonProps {
  variant?: 'primary' | 'secondary'
  children: React.ReactNode
}

export function Button({ variant = 'primary', children }: ButtonProps) {
  const className = variant === 'primary' ? styles.primary : styles.secondary
  
  return <button className={className}>{children}</button>
}

Naming Conventions

camelCase (Recommended):

css
.buttonPrimary { /* ... */ }
.buttonSecondary { /* ... */ }
typescript
import styles from './Button.module.css'
styles.buttonPrimary // accessed as property

kebab-case:

css
.button-primary { /* ... */ }
typescript
import styles from './Button.module.css'
styles['button-primary'] // accessed with brackets

Composition with composes

css
/* base.module.css */
.base {
  padding: 8px 16px;
  border-radius: 4px;
  font-weight: 500;
}

/* Button.module.css */
.primary {
  composes: base from './base.module.css';
  background-color: #3b82f6;
  color: white;
}

Global Selectors

css
/* Container.module.css */
.container {
  padding: 20px;
}

.container :global(p) {
  margin-bottom: 12px;
}

.container :global(a) {
  color: #3b82f6;
  text-decoration: none;
}

Dynamic Class Names (with clsx/cn)

typescript
import clsx from 'clsx'
import styles from './Button.module.css'

interface ButtonProps {
  variant: 'primary' | 'secondary' | 'danger'
  size: 'sm' | 'md' | 'lg'
  disabled?: boolean
}

export function Button({ variant, size, disabled }: ButtonProps) {
  const className = clsx(
    styles.button,
    {
      [styles.primary]: variant === 'primary',
      [styles.secondary]: variant === 'secondary',
      [styles.danger]: variant === 'danger',
      [styles.small]: size === 'sm',
      [styles.medium]: size === 'md',
      [styles.large]: size === 'lg',
      [styles.disabled]: disabled,
    }
  )

  return <button className={className} disabled={disabled} />
}

CSS Modules with TypeScript

bash
npm install -D typed-css-modules

Design Tokens

Design tokens are the foundation of scalable, consistent design systems.

Token File Format (JSON)

json
{
  "colors": {
    "primary": "#3b82f6",
    "secondary": "#6b7280",
    "success": "#10b981",
    "danger": "#ef4444",
    "warning": "#f59e0b",
    "light": "#f3f4f6",
    "dark": "#1f2937"
  },
  "spacing": {
    "xs": "4px",
    "sm": "8px",
    "md": "16px",
    "lg": "24px",
    "xl": "32px",
    "2xl": "48px"
  },
  "typography": {
    "fontSize": {
      "xs": "12px",
      "sm": "14px",
      "base": "16px",
      "lg": "18px",
      "xl": "20px",
      "2xl": "24px"
    },
    "fontWeight": {
      "light": 300,
      "normal": 400,
      "medium": 500,
      "semibold": 600,
      "bold": 700
    }
  }
}

CSS Custom Properties (Variables)

css
/* globals.css */
:root {
  /* Colors */
  --color-primary: #3b82f6;
  --color-secondary: #6b7280;
  --color-success: #10b981;
  --color-danger: #ef4444;

  /* Spacing */
  --spacing-xs: 4px;
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 24px;
  --spacing-xl: 32px;

  /* Typography */
  --font-size-base: 16px;
  --font-weight-medium: 500;
  --font-weight-bold: 700;

  /* Border Radius */
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 12px;

  /* Shadows */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
  --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
  --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
}

/* Dark mode */
@media (prefers-color-scheme: dark) {
  :root {
    --color-primary: #60a5fa;
    --color-secondary: #9ca3af;
  }
}

Usage:

css
.button {
  padding: var(--spacing-md);
  background-color: var(--color-primary);
  border-radius: var(--radius-md);
  font-weight: var(--font-weight-medium);
  box-shadow: var(--shadow-md);
}

Style Dictionary (Multi-Platform)

For generating tokens across platforms (web, iOS, Android):

bash
npm install -D style-dictionary

See design-tokens.md for setup.

CSS-in-JS Solutions

vanilla-extract (Zero-Runtime, Recommended for App Router)

Setup:

bash
npm install @vanilla-extract/css
npm install -D @vanilla-extract/next-plugin

Configuration:

typescript
// next.config.ts
import { createVanillaExtractPlugin } from '@vanilla-extract/next-plugin'

const withVanillaExtract = createVanillaExtractPlugin()

export default withVanillaExtract({})

Usage:

typescript
// Button.css.ts
import { style, globalStyle } from '@vanilla-extract/css'

export const button = style({
  padding: '8px 16px',
  borderRadius: '4px',
  backgroundColor: '#3b82f6',
  color: 'white',
  border: 'none',
  cursor: 'pointer',
  fontWeight: 500,
  ':hover': {
    backgroundColor: '#2563eb',
  },
})

export const primary = style([button, {
  backgroundColor: '#3b82f6',
}])

export const secondary = style([button, {
  backgroundColor: '#6b7280',
}])
typescript
// Button.tsx
import * as styles from './Button.css'

export function Button({ variant = 'primary' }) {
  const className = variant === 'primary' ? styles.primary : styles.secondary
  return <button className={className}>Click me</button>
}

Advantages:

  • Zero runtime CSS
  • Type-safe style definitions
  • Full server component support
  • CSS output is deterministic and optimized

Panda CSS (Build-Time CSS-in-JS)

Setup:

bash
npm install @pandacss/dev
npx panda init

Usage:

typescript
// Button.tsx
import { css } from '@pandacss/preset-base'

export function Button() {
  return (
    <button className={css({
      px: '16px',
      py: '8px',
      bg: 'blue.500',
      color: 'white',
      rounded: 'md',
      fontWeight: 'medium',
      _hover: { bg: 'blue.600' }
    })}>
      Click me
    </button>
  )
}

Advantages:

  • Responsive design with array syntax
  • Built-in dark mode support
  • No runtime overhead
  • Atomic CSS output

styled-components (Runtime CSS-in-JS)

Setup:

bash
npm install styled-components
npm install -D @types/styled-components

Configuration:

typescript
// next.config.ts
const config = {
  compiler: {
    styledComponents: true,
  },
}

Usage:

typescript
// Button.tsx
'use client'

import styled from 'styled-components'

const StyledButton = styled.button`
  padding: 8px 16px;
  border-radius: 4px;
  background-color: #3b82f6;
  color: white;
  border: none;
  cursor: pointer;
  font-weight: 500;

  &:hover {
    background-color: #2563eb;
  }
`

export function Button() {
  return <StyledButton>Click me</StyledButton>
}

Limitations:

  • Requires 'use client' directive
  • Runtime CSS injection overhead
  • Not ideal for large-scale apps

Global Styles

css
/* app/globals.css */
@import "tailwindcss";

/* Reset and base styles */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  font-size: 16px;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  line-height: 1.5;
  color: var(--color-foreground);
  background-color: var(--color-background);
}

/* Typography hierarchy */
h1, h2, h3, h4, h5, h6 {
  font-weight: 600;
  line-height: 1.2;
  margin-bottom: 1rem;
}

h1 { font-size: 2rem; }
h2 { font-size: 1.5rem; }
h3 { font-size: 1.25rem; }

/* Links */
a {
  color: var(--color-primary);
  text-decoration: none;
  transition: color 0.2s;
}

a:hover {
  color: var(--color-primary-dark);
}

Decision Matrix: Which Approach to Use?

Choose Tailwind if:

  • Starting a new project
  • Want rapid prototyping
  • Team knows utility-first CSS
  • Need responsive design out of the box

Choose CSS Modules if:

  • Want component-scoped, isolated styles
  • Prefer traditional CSS
  • Don't need complex theme switching
  • Want zero runtime overhead

Choose vanilla-extract if:

  • Building a design system
  • Want type-safe styles
  • Need zero-runtime CSS
  • Prioritize app performance

Choose Panda CSS if:

  • Want CSS-in-JS with no runtime cost
  • Need responsive design syntax
  • Want modern DX with dev tools
  • Building scalable applications

Choose styled-components if:

  • Need dynamic theming based on state
  • Comfortable with runtime CSS
  • Building component libraries
  • Need full JS-like expressions in styles

Choose CSS variables if:

  • Want lightweight token-based theming
  • Already using CSS or Tailwind
  • Need simple dark mode switching
  • Want minimal bundle size

Combining Approaches

Many projects use multiple approaches:

typescript
// components/Card.tsx
'use client'

import styles from './Card.module.css' // CSS Modules for structure
import { classNames } from '@/lib/utils' // utility function
import './Card.global.css' // Global theme-aware styles with CSS variables

export function Card({ children }) {
  return (
    <div className={classNames(styles.card, 'dark:bg-slate-900')}>
      {/* Tailwind for responsive utilities */}
      {/* CSS Modules for component structure */}
      {/* CSS variables for theme colors */}
      {children}
    </div>
  )
}

This hybrid approach balances:

  • Performance (CSS Modules + Tailwind)
  • Flexibility (CSS variables)
  • DX (utility classes + scoped styles)

Performance Optimization

Bundle Size

  • Tailwind v4: ~8-15kb gzipped (CSS-first, optimized)
  • CSS Modules: ~0kb (native CSS)
  • vanilla-extract: ~0kb (extracted to CSS at build time)
  • Panda CSS: ~5-10kb gzipped (atomic CSS)
  • styled-components: ~12-16kb gzipped (runtime cost)

Server Component Support

ApproachServer ComponentsNotes
TailwindFull✓ Recommended
CSS ModulesFull✓ Best practice
vanilla-extractFull✓ Zero-runtime
Panda CSSFull✓ Type-safe
styled-componentsLimited⚠ Requires "use client"

Resources

Next Steps

See the reference files for more detailed patterns: