Crystal
Getting Started

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:

PackageWhat Goes HereExamples
crystal-tokensDesign tokens, color palettes, spacing scaleNew theme, spacing value, CSS custom property
crystal-core(Deleted)Merged into crystal-ui
crystal-uiAll React components, hooks, utilitiesNew 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 a crystallization CVA variant
  • Use data-slot attributes — Every rendered element gets data-slot="component-name" for DOM introspection
  • Use cn() for className merging — Import from ../utils/cn
  • Accept className prop — Allow consumers to extend styling
  • Respect reduced motion — Animation components must check prefers-reduced-motion (use usePrefersReducedMotion hook)

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 build

This 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

  1. Use the shadcn CLI or manually create the file in packages/crystal-ui/src/ui/
  2. 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)]
  3. Optionally add a crystallization CVA variant for per-preset behavior
  4. Add data-slot to the root element
  5. Export from packages/crystal-ui/src/ui/index.ts
  6. Add to the appropriate grouped docs page (forms, overlays, data-display, nav-components, feedback, or layout-components)

Naming Conventions

PatternConventionExample
Crystal componentsCrystal prefixCrystalTile, CrystalNav
shadcn/ui componentsNo prefixButton, Dialog, Badge
Hooksuse prefixuseCrystalTheme, useSidebar
CSS variables--crystal-* namespace--crystal-color-crystal-accent-primary
CVA variantsCamel casebuttonVariants, badgeVariants
Storybook storiesCategory/NamePrimitives/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-slot attribute
  • 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-motion if animated