This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Always follow the jac-shadcn skills at .claude/skills/jac-shadcn/ when writing or modifying components:
| Rule | File |
|---|---|
| Styling (semantic colors, cn(), size-*, no dark: overrides) | .claude/skills/jac-shadcn/rules/styling.md |
| Composition (Groups, Titles, Card, ButtonGroup nesting) | .claude/skills/jac-shadcn/rules/composition.md |
| Icons (HugeIcons only, HugeiconsIcon wrapper) | .claude/skills/jac-shadcn/rules/icons.md |
| Forms (Field + Label, InputGroup, ToggleGroup) | .claude/skills/jac-shadcn/rules/forms.md |
| Jac Patterns (has, glob, no destructuring, True/False) | .claude/skills/jac-shadcn/rules/jac-patterns.md |
| CLI Reference | .claude/skills/jac-shadcn/cli.md |
| Theming & Customization | .claude/skills/jac-shadcn/customization.md |
jac-shadcn is a shadcn-style UI component library and theme customizer for the Jac language. It provides UI components ported from TypeScript/React to Jac (.cl.jac), a visual theme customizer with 5 styles, 21 color themes, and 12 fonts, and a registry server that serves resolved components for jac add --shadcn.
Hosted at: https://jac-shadcn.jaseci.org
source /home/ahzan/.jacvenv/bin/activate # Required — always activate this venv first
jac start main.jac # Serves on http://localhost:8000No separate build step. The Jac compiler handles transpilation to JS + Vite bundling automatically.
jac-shadcn/
├── main.jac # Entry point — app + 3 REST endpoints + mobile sheets
├── jac.toml # Project config (npm deps, Vite plugins, Tailwind)
├── global.css # Tailwind CSS + shadcn theme variables (oklch)
├── styles/ # 5 style CSS files (cn-* token definitions, ~327 per file)
├── lib/
│ ├── utils.cl.jac # cn() utility (clsx + tailwind-merge)
│ ├── config.cl.jac # buildRegistryTheme() + RADII, PRESETS, defaults
│ ├── themes.cl.jac # 21 color themes (oklch CSS variables)
│ ├── fonts.cl.jac # 12 font families (@fontsource-variable)
│ ├── styles.cl.jac # Style metadata (names, descriptions)
│ ├── design-system-provider.cl.jac # React context — applies theme to DOM
│ └── export_service.jac # Server-side: parse CSS, resolve components, build jacpack
├── components/
│ ├── customizer.cl.jac # Right sidebar — 7 picker sub-components (desktop + mobile)
│ ├── preview-panel.cl.jac # Center — live component preview
│ ├── item-explorer.cl.jac # Left sidebar — component list (desktop + mobile)
│ ├── site-header.cl.jac # Sticky navbar — hamburger + palette icons on mobile
│ ├── site-footer.cl.jac # Footer with links
│ ├── home-page.cl.jac # Landing page
│ ├── showcases/ # Split from monolithic component-showcases.cl.jac
│ │ ├── shared.cl.jac # ShowcaseSection, ShowcaseHeader, CopyCommand + helpers
│ │ ├── router.cl.jac # ComponentShowcase dispatcher (imports all categories)
│ │ ├── form-controls.cl.jac # Button, Checkbox, Switch, RadioGroup, Slider
│ │ ├── form-inputs.cl.jac # Input, Textarea, Select, Combobox, Field, InputGroup, Label, NativeSelect, InputOTP
│ │ ├── overlay.cl.jac # Dialog, AlertDialog, Sheet, Drawer, Popover, HoverCard, Tooltip, DropdownMenu, ContextMenu, Command
│ │ ├── navigation.cl.jac # Tabs, Accordion, Breadcrumb, Pagination, NavigationMenu, Menubar, Sidebar, Collapsible
│ │ ├── display.cl.jac # Card, Badge, Avatar, Table, Calendar, Carousel, ScrollArea, AspectRatio, Resizable
│ │ ├── feedback.cl.jac # Alert, Progress, Skeleton, Spinner, Empty
│ │ ├── utility.cl.jac # Separator, Toggle, ToggleGroup, ButtonGroup, Kbd, Item
│ │ └── chart.cl.jac # Chart (Recharts)
│ └── ui/ # 53 UI components (button, card, dialog, etc.)
├── data/ # themes.json, registry.json
└── docs/ # tsx-to-jac-conversion-guide.md, jaccn-architecture.md
The system has three layers that work together. Understanding this flow is essential for any changes.
A config object with 7 fields drives the entire theme:
{ style, baseColor, theme, font, radius, menuAccent, menuColor }
buildRegistryTheme(config) is the pivot function — it merges a base color palette (4 neutrals: neutral/stone/zinc/gray) with an accent theme (17 colors like rose/blue/emerald), applies menuAccent overrides, and computes radius values. Returns { name, cssVars: { light: {...}, dark: {...} } } with 30+ oklch CSS variables.
A React Context that applies the config to the DOM via three mechanisms:
- Body classes (
useLayoutEffect): Adds.style-nova .base-color-grayto body — activates the correctstyles/style-*.cssrules - Inline CSS variables (
useLayoutEffect): Creates<style id="ds-theme-vars">injecting all 30+ CSS vars as body inline styles — highest specificity, overrides@theme inline - Font application: Sets
--font-sans+ inlinefontFamilyon bothdocumentElementandbody(three places needed for reliable switching)
Also handles menu color inversion via MutationObserver watching for .cn-menu-target elements.
Components use style-agnostic placeholder class names like cn-button, cn-card-header, cn-button-variant-default. These are NOT real CSS classes — they're resolved differently per style.
In a component (components/ui/button.cl.jac):
cva("cn-button focus-visible:border-ring ... inline-flex items-center", {
"variants": { "variant": { "default": "cn-button-variant-default bg-primary ..." } }
})In a style file (styles/style-nova.css):
.style-nova .cn-button { @apply rounded-lg border border-transparent text-sm font-medium; }
.style-nova .cn-button-variant-default { @apply bg-primary text-primary-foreground; }In another style (styles/style-vega.css):
.style-vega .cn-button { @apply rounded-md border border-transparent text-sm font-medium; }Same component code → different visual output per style. The .style-* body class determines which CSS rules activate.
Server-side Python that produces standalone themed projects. The pipeline:
parse_style_css(style)— regex-extracts all cn-* → Tailwind mappings from the style CSS fileresolve_component(source, map)— replaces cn-* tokens with resolved Tailwind classes (sorted longest-first to avoid partial matches)generate_global_css(font, cssVars)— produces themed global.css with baked-in color varsbuild_jacpack()— assembles all resolved files into a downloadable jacpack
Exported projects have no cn- tokens* — all classes are resolved to concrete Tailwind.
GET /registry— Component manifest (names, npmDeps, peerComponents)GET /component/{name}?style=nova— Single resolved component file contentGET /jacpack?style=nova&theme=rose&font=inter&...— Complete .jacpack forjac create --use URL
The CLI plugin lives at /home/ahzan/Documents/jaseci/jaseci/jac-plugins/jac-shadcn/:
jac add --shadcn button card— adds components from this registryjac remove --shadcn button— removes componentsjac create --use jac-shadcn— scaffolds themed project
| Layer | Technology |
|---|---|
| Language | Jac (.cl.jac for client-side JSX) |
| UI Primitives | Radix UI (radix-ui ^1.4.3), Base UI (@base-ui/react ^1.2.0) |
| Styling | Tailwind CSS v4 + class-variance-authority (CVA) |
| Icons | HugeIcons (@hugeicons/react + @hugeicons/core-free-icons) |
| CSS Utilities | clsx + tailwind-merge via cn() |
| Build | Vite (auto-configured by Jac compiler) |
| React | v18.3.1 (pinned by jac-client-node) — no ref-as-prop (React 19 feature) |
Full documentation: docs/tsx-to-jac-conversion-guide.md
glob _buttonVariants: Any = cva(
"cn-button focus-visible:border-ring inline-flex items-center ...",
{
"variants": {
"variant": {
"default": "cn-button-variant-default bg-primary text-primary-foreground ...",
"outline": "cn-button-variant-outline border-border bg-background ..."
},
"size": {
"default": "cn-button-size-default h-8 gap-1.5 px-2.5 ..."
}
},
"defaultVariants": { "variant": "default", "size": "default" }
}
);def:pub Button(props: Any) -> JsxElement {
variant = props.variant or "default";
size = props.size or "default";
variantsFn = _buttonVariants;
computedClass = cn(variantsFn.call(None, {"variant": variant, "size": size, "className": props.className}));
return <button className={computedClass} {...props}>{props.children}</button>;
}def:pub Card(props: Any) -> JsxElement {
return <div {...props} data-slot="card" className={cn("cn-card bg-card ...", props.className)} />;
}
def:pub CardHeader(props: Any) -> JsxElement {
return <div {...props} data-slot="card-header" className={cn("cn-card-header ...", props.className)} />;
}Jac compiles components as plain functions. For Radix triggers, apply buttonVariants() directly:
<DropdownMenuTrigger className={buttonVariants().call(None, {"variant": "ghost", "size": "icon"})}>
Click
</DropdownMenuTrigger>has theme: str = "light";
# Mutable — read/write directly, no setter function, no stale closure issues- No tuple unpacking:
a, b = func()is invalid - No nested
definsidedef: Define all helpers at module level - No docstrings inside function bodies: Place before
def - No JSX comments: Remove all
{/* */}and# commentfrom JSX return blocks - String
?in JSX: Must wrap:{"?"}not bare? - String
&in JSX: Must wrap:{"Privacy & Security"}not bare& True/False/None: Capitalized (Python-style)Reflect.construct(): Use instead ofnewforDate,WebSocket,Map, etc.- Lambda callback gotcha: Function params inside lambdas compile to
new fn(). Assign to local var and use.call(None, args). globfor module-level constants:glob _frameworks: list = [...]— notconst- Boolean in JSX: Use
Boolean(anchor)not!!anchor "\n"bug: Compiles to literal"\\n". Usestyle.setProperty()for CSS that needs newlines.- Physical CSS properties: Use
pt-4 pb-4notpy-4— ensurespt-0overrides cleanly withtwMerge
- Read the TSX source from the shadcn registry
- Follow
docs/tsx-to-jac-conversion-guide.mdstep by step - Use
cn-*class tokens (style-agnostic) in base components — one token per semantic element - Add corresponding
cn-*definitions to all 5 style CSS files (styles/style-*.css) - Verify resolution: 0 unresolved cn-* tokens across all styles
- Register in
data/registry.jsonwith npmDeps and peerComponents - Add showcase in the appropriate
components/showcases/<category>.cl.jacfile - Add to export_service.jac component list for jacpack generation
- Test in customizer: renders correctly, all 5 style switches work