Contributing to Crystal
Guidelines for adding components, themes, and tokens to the Crystal Design System.
Package Structure
Crystal is split across three packages — know where your change belongs:
| Package | What Goes Here | Examples |
|---|---|---|
crystal-tokens | Design tokens, color palettes, spacing scale | New theme, spacing value, CSS custom property |
crystal-core | (Deleted) | Merged into crystal-ui |
crystal-ui | All React components, hooks, utilities | New component, shadcn/ui additions, animation wrappers |
Adding a New Component
1. Choose the Right Directory
packages/crystal-ui/src/
├── primitives/ # Core visual building blocks (CrystalTile, CrystalCard, CrystalChip)
├── navigation/ # Navigation patterns (CrystalNav, CrystalNavLink)
├── animation/ # GSAP-powered motion components (CrystalTextReveal, CrystalScrollSmoother)
├── devices/ # Device mockup frames (CrystalDevicePhone, CrystalDeviceBrowser)
├── containers/ # Layout containers (CrystalGrid)
├── sections/ # Full-page sections (CrystalFullscreenSlider)
├── components/ # Higher-level composed components (CrystalSectionHeader)
├── provider/ # CrystalProvider and context
├── hooks/ # Shared hooks (useMobile, useCrystalNavTimelines)
└── ui/ # shadcn/ui components (Button, Dialog, etc.)2. Component Requirements
Every Crystal component must:
- Use Crystal tokens — Reference
--crystal-*CSS variables or semantic Tailwind classes (bg-primary,text-foreground), never hardcoded colors - Support crystallization — Use
var(--radius),var(--border-width),var(--motion-duration)for responsive styling. Components with distinct per-crystallization behavior should accept acrystallizationCVA variant - Use
data-slotattributes — Every rendered element getsdata-slot="component-name"for DOM introspection - Use
cn()for className merging — Import from../utils/cn - Accept
classNameprop — Allow consumers to extend styling - Respect reduced motion — Animation components must check
prefers-reduced-motion(useusePrefersReducedMotionhook)
3. Export from Barrel
Add your component to the appropriate barrel file. Crystal components export from packages/crystal-ui/src/index.ts. shadcn/ui components export from packages/crystal-ui/src/ui/index.ts.
4. Write Documentation
Create an MDX page in apps/crystal-design-system/content/docs/components/:
---
title: ComponentName
description: One-line description.
---
Brief description of what the component does.
<StorybookLink story="category-componentname" />
## Import
\`\`\`tsx
import { ComponentName } from '@artasce/crystal-ui';
\`\`\`
## Usage
\`\`\`tsx
<ComponentName prop="value">Content</ComponentName>
\`\`\`
## Props
<TokenTable
title="ComponentName Props"
tokens={[
{ name: 'propName', variable: 'type', value: 'default', description: 'What it does' },
]}
/>5. Write a Storybook Story
Create a story in apps/crystal-design-system/stories/:
import type { Meta, StoryObj } from '@storybook/react';
import { ComponentName } from '@artasce/crystal-ui';
const meta: Meta<typeof ComponentName> = {
title: 'Category/ComponentName',
component: ComponentName,
};
export default meta;
type Story = StoryObj<typeof ComponentName>;
export const Default: Story = {
args: { /* default props */ },
};Adding a New Theme
1. Define Tokens
Add a new theme file in packages/crystal-tokens/src/themes/:
{
"crystal": {
"color": {
"crystal-background-primary": { "value": "#..." },
"crystal-foreground-primary": { "value": "#..." },
"crystal-accent-primary": { "value": "#..." }
}
}
}Every theme must define the full token set (backgrounds, foregrounds, accents, borders, status colors).
2. Build Tokens
pnpm --filter @artasce/crystal-tokens buildThis generates CSS output in dist/css/themes/<theme>.css.
3. Register in CSS Bridge
Add the theme's [data-crystal="<name>"] selector to apps/crystal-design-system/app/globals.css so Fumadocs colors update.
4. Add Type
Add the theme name to CrystalTheme type in packages/crystal-ui/src/types.ts.
5. Document
Create a theme page in content/docs/themes/<name>.mdx using the established pattern with <ColorPalette />.
Adding a shadcn/ui Component
- Use the shadcn CLI or manually create the file in
packages/crystal-ui/src/ui/ - Replace hardcoded radius/border/shadow/timing with Crystal CSS variables:
rounded-lg->rounded-[var(--radius)]border->border-[length:var(--border-width)]shadow-md->shadow-[var(--shadow)]duration-200->duration-[var(--motion-duration)]
- Optionally add a
crystallizationCVA variant for per-preset behavior - Add
data-slotto the root element - Export from
packages/crystal-ui/src/ui/index.ts - Add to the appropriate grouped docs page (forms, overlays, data-display, nav-components, feedback, or layout-components)
Naming Conventions
| Pattern | Convention | Example |
|---|---|---|
| Crystal components | Crystal prefix | CrystalTile, CrystalNav |
| shadcn/ui components | No prefix | Button, Dialog, Badge |
| Hooks | use prefix | useCrystalTheme, useSidebar |
| CSS variables | --crystal-* namespace | --crystal-color-crystal-accent-primary |
| CVA variants | Camel case | buttonVariants, badgeVariants |
| Storybook stories | Category/Name | Primitives/CrystalTile, UI/Button |
Code Quality Checklist
Before submitting a component:
- Uses Crystal tokens (no hardcoded colors)
- Crystallization-responsive (CSS variable radius/border/shadow/timing)
- Has
data-slotattribute - Exported from barrel file
- TypeScript strict mode passes (
tsc --noEmit) - MDX documentation page created
- Storybook story created
- Tested across at least 2 themes (one light, one dark)
- Respects
prefers-reduced-motionif animated