# CSS Styling Approaches Reference ## Comparison Matrix | Approach | Runtime | Bundle Size | Learning Curve | Dynamic Styles | SSR | |----------|---------|-------------|----------------|----------------|-----| | CSS Modules | None | Minimal | Low | Limited | Yes | | Tailwind | None | Small (purged) | Medium | Via classes | Yes | | styled-components | Yes | Medium | Medium | Full | Yes* | | Emotion | Yes | Medium | Medium | Full | Yes | | Vanilla Extract | None | Minimal | High | Limited | Yes | ## CSS Modules Scoped CSS with zero runtime overhead. ### Setup ```tsx // Button.module.css .button { padding: 0.5rem 1rem; border-radius: 0.375rem; font-weight: 500; transition: background-color 0.2s; } .primary { background-color: #2563eb; color: white; } .primary:hover { background-color: #1d4ed8; } .secondary { background-color: #f3f4f6; color: #1f2937; } .secondary:hover { background-color: #e5e7eb; } .small { padding: 0.25rem 0.5rem; font-size: 0.875rem; } .large { padding: 0.75rem 1.5rem; font-size: 1.125rem; } ``` ```tsx // Button.tsx import styles from './Button.module.css'; import { clsx } from 'clsx'; interface ButtonProps { variant?: 'primary' | 'secondary'; size?: 'small' | 'medium' | 'large'; children: React.ReactNode; onClick?: () => void; } export function Button({ variant = 'primary', size = 'medium', children, onClick, }: ButtonProps) { return ( ); } ``` ### Composition ```css /* base.module.css */ .visuallyHidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); border: 0; } /* Button.module.css */ .srOnly { composes: visuallyHidden from './base.module.css'; } ``` ## Tailwind CSS Utility-first CSS with design system constraints. ### Class Variance Authority (CVA) ```tsx import { cva, type VariantProps } from 'class-variance-authority'; import { cn } from '@/lib/utils'; const buttonVariants = cva( // Base styles 'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', { variants: { variant: { default: 'bg-primary text-primary-foreground hover:bg-primary/90', destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90', outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', ghost: 'hover:bg-accent hover:text-accent-foreground', link: 'text-primary underline-offset-4 hover:underline', }, size: { default: 'h-10 px-4 py-2', sm: 'h-9 rounded-md px-3', lg: 'h-11 rounded-md px-8', icon: 'h-10 w-10', }, }, defaultVariants: { variant: 'default', size: 'default', }, } ); interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { asChild?: boolean; } const Button = React.forwardRef( ({ className, variant, size, ...props }, ref) => { return ( ``` ## Emotion Flexible CSS-in-JS with object and template syntax. ```tsx /** @jsxImportSource @emotion/react */ import { css, Theme, ThemeProvider, useTheme } from '@emotion/react'; import styled from '@emotion/styled'; // Theme typing declare module '@emotion/react' { export interface Theme { colors: { primary: string; background: string; text: string; }; spacing: (factor: number) => string; } } const theme: Theme = { colors: { primary: '#2563eb', background: '#ffffff', text: '#1f2937', }, spacing: (factor: number) => `${factor * 0.25}rem`, }; // Object syntax const cardStyles = (theme: Theme) => css({ backgroundColor: theme.colors.background, padding: theme.spacing(4), borderRadius: '0.5rem', boxShadow: '0 1px 3px rgba(0, 0, 0, 0.1)', }); // Template literal syntax const buttonStyles = css` padding: 0.5rem 1rem; border-radius: 0.375rem; font-weight: 500; &:hover { opacity: 0.9; } `; // Styled component with theme const Card = styled.div` background-color: ${({ theme }) => theme.colors.background}; padding: ${({ theme }) => theme.spacing(4)}; border-radius: 0.5rem; `; // Component with css prop function Alert({ children }: { children: React.ReactNode }) { const theme = useTheme(); return (
{children}
); } // Usage Important message ``` ## Vanilla Extract Zero-runtime CSS-in-JS with full type safety. ```tsx // styles.css.ts import { style, styleVariants, createTheme } from '@vanilla-extract/css'; import { recipe, type RecipeVariants } from '@vanilla-extract/recipes'; // Theme contract export const [themeClass, vars] = createTheme({ color: { primary: '#2563eb', secondary: '#64748b', background: '#ffffff', text: '#1f2937', }, space: { small: '0.5rem', medium: '1rem', large: '1.5rem', }, radius: { small: '0.25rem', medium: '0.375rem', large: '0.5rem', }, }); // Simple style export const container = style({ padding: vars.space.medium, backgroundColor: vars.color.background, }); // Style variants export const text = styleVariants({ primary: { color: vars.color.text }, secondary: { color: vars.color.secondary }, accent: { color: vars.color.primary }, }); // Recipe (like CVA) export const button = recipe({ base: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontWeight: 500, borderRadius: vars.radius.medium, transition: 'background-color 0.2s', cursor: 'pointer', border: 'none', ':disabled': { opacity: 0.5, cursor: 'not-allowed', }, }, variants: { variant: { primary: { backgroundColor: vars.color.primary, color: 'white', ':hover': { backgroundColor: '#1d4ed8', }, }, secondary: { backgroundColor: '#f3f4f6', color: vars.color.text, ':hover': { backgroundColor: '#e5e7eb', }, }, }, size: { small: { padding: '0.25rem 0.75rem', fontSize: '0.875rem', }, medium: { padding: '0.5rem 1rem', fontSize: '1rem', }, large: { padding: '0.75rem 1.5rem', fontSize: '1.125rem', }, }, }, defaultVariants: { variant: 'primary', size: 'medium', }, }); export type ButtonVariants = RecipeVariants; ``` ```tsx // Button.tsx import { button, type ButtonVariants, themeClass } from './styles.css'; interface ButtonProps extends ButtonVariants { children: React.ReactNode; onClick?: () => void; } export function Button({ variant, size, children, onClick }: ButtonProps) { return ( ); } // App.tsx - wrap with theme function App() { return (
); } ``` ## Performance Considerations ### Critical CSS Extraction ```tsx // Next.js with styled-components // pages/_document.tsx import Document, { DocumentContext } from 'next/document'; import { ServerStyleSheet } from 'styled-components'; export default class MyDocument extends Document { static async getInitialProps(ctx: DocumentContext) { const sheet = new ServerStyleSheet(); const originalRenderPage = ctx.renderPage; try { ctx.renderPage = () => originalRenderPage({ enhanceApp: (App) => (props) => sheet.collectStyles(), }); const initialProps = await Document.getInitialProps(ctx); return { ...initialProps, styles: [initialProps.styles, sheet.getStyleElement()], }; } finally { sheet.seal(); } } } ``` ### Code Splitting Styles ```tsx // Dynamically import heavy styled components import dynamic from 'next/dynamic'; const HeavyChart = dynamic(() => import('./HeavyChart'), { loading: () => , ssr: false, }); ```