forge/CLAUDE.md
2025-12-26 22:22:08 -08:00

4.3 KiB

Forge - Structured CSS Authoring

A typed, local, variant-driven way to author CSS. Compiles to real CSS but removes the chaos: no global conflicts, no selector gymnastics, no inline styles. Built for Hono JSX with SSR support.

The Problem

CSS is hostile to humans at scale: global namespace, no markup-to-definition link, requires inline styles for per-instance variance, silent conflicts, selector complexity.

The Solution

  • Local styles - Attached via generated class names, not strings
  • Parts - Named sub-components replace selectors (no .Button > .Icon nonsense)
  • Variants - Typed parameters replace inline styles (no style={{ color: x }})
  • Deterministic - Known merge order, dev warnings for conflicts
  • Compiles to CSS - Not a new language, not runtime CSS-in-JS, just organized CSS generation

Core Concepts

define(name?, def) - Creates a styled component. Returns a component function.

  • Accepts CSS properties in camelCase (auto-converts to kebab-case)
  • Numbers auto-converted to px (except unitless props like opacity, zIndex)
  • Generates CSS classes and registers styles globally

Parts - Sub-components within a component (e.g., Header, Body, Footer)

  • Defined via parts: { PartName: { ...styles } }
  • Accessible in render as parts.PartName
  • Generate classes like ComponentName_PartName

Variants - Conditional styling based on props

  • Boolean: variants: { active: { color: 'blue' } }<Component active />
  • Keyed: variants: { size: { small: {...}, large: {...} } }<Component size="small" />
  • Work on both root and parts
  • Generate classes like ComponentName.variant-key

States - Pseudo-selectors like hover, focus

  • states: { hover: { background: 'blue' } }.Class:hover { ... }

Custom Render - Override default rendering

  • render({ props, parts }) { return <parts.Root>...</parts.Root> }
  • Compose parts manually, pass props through

File Structure

  • src/index.tsx - Main implementation (define, Styles, CSS generation)
  • src/types.ts - TagDef type with all CSS properties, helper sets
  • examples/ - REFERENCE THESE for real-world usage patterns:
    • helpers.tsx - Layout wrapper, reusable components (Body, Header, ThemeToggle)
    • index.tsx - Landing page with grid, cards, parts, and custom render
    • button.tsx - Button variants (intent, size, disabled)
    • profile.tsx - Complex component with multiple parts and variants
    • navigation.tsx - Tabs, pills, breadcrumbs, vertical nav patterns
  • src/tests/ - Comprehensive test suite with test helpers

Implementation Details

  • Static CSS generation - CSS created at component definition time, not runtime
  • Global styles registry - styles object stores all CSS as plain objects
  • Styles component - Renders <style> tag with all registered CSS (include in HTML <head>)
  • Real CSS classes - Generates actual CSS selectors, not inline styles or CSS-in-JS
  • Class naming:
    • Root: ComponentName
    • Parts: ComponentName_PartName
    • Variants: ComponentName.variant-key or just variant for boolean
    • States: .ClassName:state
  • No duplicate names - Throws if same name registered twice
  • Anonymous components - Auto-named Def1, Def2, etc. when name omitted

Usage Pattern

IMPORTANT: Check examples/ for real-world patterns before writing new components.

import { define } from 'forge'

export const Button = define('Button', {
  base: 'button',  // HTML tag (default: 'div')
  padding: 20,
  background: 'blue',

  states: {
    hover: { background: 'darkblue' }
  },

  variants: {
    danger: { background: 'red' },  // boolean
    size: {                          // keyed
      small: { padding: 10 },
      large: { padding: 30 }
    }
  }
})

// <Button>Click</Button>
// <Button danger>Danger!</Button>
// <Button size="large">Big</Button>

For complex patterns (parts with variants, custom render, states within variants), see:

  • examples/index.tsx - ExampleCard with parts, custom render, nested variants
  • examples/profile.tsx - Multi-part component with size/theme variants
  • examples/navigation.tsx - Multiple component patterns (tabs, pills, breadcrumbs)

Testing

Tests use Bun's test runner. Helpers in src/tests/test_helpers.ts for rendering JSX to HTML strings and parsing CSS.