Skip to content

Spec Schema Reference

Complete reference for the ComponentSpec schema. All component specs are validated against this schema using Zod.

ComponentSpec

The root object for a component spec.

FieldTypeRequiredDefaultDescription
namestringyes--Component name. The only required field
descriptionstringno--Human-readable description
categorystringno--Component category (e.g., actions, forms, feedback, navigation, data-display, layout)
complexity"simple" | "moderate" | "complex"no"moderate"Complexity classification
propsProp[]no[]Component props
variantsVariant[]no[]Variant axes with allowed values
defaultVariantsRecord<string, string>no{}Default values for variants
slotsSlot[]no[]Named slots
anatomyAnatomyPart[]no[]Named sub-parts that make up the component
tokenMappingRecord<string, string>no{}Maps part.property[:state][variant=value] to {token.path} references
statesstring[]no[]Interactive states (hover, focus, active, disabled, etc.)
eventsEvent[]no[]Event handlers the component emits
dependenciesstring[]no[]Component dependencies
accessibilityAccessibilityno--Accessibility requirements
guidelinesstring[]no[]Implementation guidelines

Prop

Defines a component prop.

FieldTypeRequiredDefaultDescription
namestringyes--Prop name
typestringyes--Prop type (e.g., string, boolean, number, () => void)
requiredbooleannofalseWhether the prop is required
defaultunknownno--Default value
descriptionstringno--Human-readable description

Example:

json
{
  "name": "variant",
  "type": "string",
  "required": false,
  "default": "primary",
  "description": "Visual style variant"
}

Variant

Defines a variant axis with its allowed values.

FieldTypeRequiredDefaultDescription
namestringyes--Variant axis name
valuesstring[]yes--Allowed values for this variant
descriptionstringno--Human-readable description

Example:

json
{
  "name": "size",
  "values": ["sm", "md", "lg"],
  "description": "Controls the size of the component"
}

Slot

Defines a named slot for content injection.

FieldTypeRequiredDefaultDescription
namestringyes--Slot name
descriptionstringno--Human-readable description
requiredbooleannofalseWhether the slot is required

Example:

json
{
  "name": "icon",
  "description": "Leading icon slot",
  "required": false
}

AnatomyPart

Defines a named sub-part of the component.

FieldTypeRequiredDefaultDescription
namestringyes--Part name
descriptionstringno--Human-readable description
requiredbooleannotrueWhether the part is required

Example:

json
{
  "name": "root",
  "description": "Outer wrapper element",
  "required": true
}

Event

Defines an event handler that the component emits.

FieldTypeRequiredDefaultDescription
namestringyes--Event name
descriptionstringno--Human-readable description

Example:

json
{
  "name": "onClick",
  "description": "Fired when the component is clicked"
}

Accessibility

Defines accessibility requirements.

FieldTypeRequiredDefaultDescription
rolestringno--ARIA role (e.g., button, dialog, tablist)
ariaAttributesstring[]no[]Required ARIA attributes
keyboardInteractionsKeyboardInteraction[]no[]Expected keyboard interactions

KeyboardInteraction

FieldTypeRequiredDescription
keystringyesKey name (e.g., Enter, Space, Escape, ArrowDown)
descriptionstringyesWhat the key does

Example:

json
{
  "role": "dialog",
  "ariaAttributes": ["aria-modal", "aria-labelledby"],
  "keyboardInteractions": [
    { "key": "Escape", "description": "Closes the dialog" },
    { "key": "Tab", "description": "Moves focus to the next focusable element" }
  ]
}

Complete example

A full spec exercising all fields:

json
{
  "name": "Dialog",
  "description": "Modal dialog for focused interactions",
  "category": "feedback",
  "complexity": "complex",
  "props": [
    { "name": "open", "type": "boolean", "required": true },
    { "name": "onClose", "type": "() => void", "required": true },
    { "name": "title", "type": "string", "required": false },
    { "name": "size", "type": "string", "required": false, "default": "md" }
  ],
  "variants": [{ "name": "size", "values": ["sm", "md", "lg", "full"] }],
  "defaultVariants": {
    "size": "md"
  },
  "slots": [
    { "name": "header", "description": "Dialog header content" },
    { "name": "footer", "description": "Dialog footer with actions" }
  ],
  "anatomy": [
    { "name": "trigger", "description": "Element that opens the dialog", "required": true },
    { "name": "overlay", "description": "Background overlay", "required": true },
    { "name": "content", "description": "Dialog content area", "required": true },
    { "name": "header", "description": "Dialog header", "required": false },
    { "name": "footer", "description": "Dialog footer", "required": false },
    { "name": "close", "description": "Close button", "required": true }
  ],
  "tokenMapping": {
    "content.background": "{color.background}",
    "overlay.background": "{color.overlay}",
    "content.boxShadow": "{shadow.lg}",
    "content.borderRadius": "{borderRadius.lg}",
    "content.padding": "{spacing.lg}",
    "overlay.zIndex": "{zIndex.modal}"
  },
  "states": ["open", "closed"],
  "events": [{ "name": "onClose", "description": "Fired when the dialog is closed" }],
  "dependencies": ["Button"],
  "accessibility": {
    "role": "dialog",
    "ariaAttributes": ["aria-modal", "aria-labelledby", "aria-describedby"],
    "keyboardInteractions": [
      { "key": "Escape", "description": "Closes the dialog" },
      { "key": "Tab", "description": "Cycles focus within the dialog" }
    ]
  },
  "guidelines": [
    "Always trap focus within the dialog when open",
    "Return focus to the trigger element when closed",
    "Provide a visible close button in addition to Escape key"
  ]
}

Validation behavior

Specs are validated using Zod's safeParse. The schema applies defaults for missing optional fields:

  • complexity defaults to "moderate"
  • required in props and slots defaults to false; required in anatomy parts defaults to true
  • Array fields (props, variants, slots, anatomy, states, events, dependencies, guidelines) default to []
  • defaultVariants and tokenMapping default to {}

WARNING

When using applyPreset() programmatically, overrides use shallow merge, not deep merge. Passing props: [...] replaces all preset props, it does not append to them.