platform.yml Reference

Every platform is defined by a single platform.yml file. This page documents every field, its type, default value, and purpose.

Top-Level Fields

FieldTypeRequiredDescription
namestringYesDisplay name shown in the UI (e.g., "React", "Static HTML")
descriptionstringNoShort description of the platform
languagestringNoProgramming language (e.g., "typescript", "javascript")
frameworkstringYesFramework identifier. Built-in: "react", "vue", "lit", "svelte". Custom platforms use any string.
versionstringNoFramework version for documentation (e.g., "18.x")
agentRulesstring (multiline)NoFramework-specific instructions for the AI agent. Injected into system prompts. If omitted, falls back to built-in rules for known frameworks.
indexExportTemplatestringNoTemplate for barrel export files. Placeholders: {ComponentName}, {componentFile}, {baseNoExt}.

paths

FieldDefaultDescription
componentsDir"src/components"Directory where components are stored, relative to ds-lib/
recipesDir"src/recipes"Directory where non-exported Storybook recipes and composition examples are stored, relative to ds-lib/
componentDirthe component's nameHow a component name maps to a directory under componentsDir. Set "" for a flat library whose files sit directly in it. Omit it and the directory is the component's name, the default every Spektral project uses.

output

File naming patterns. Each accepts a name token that is replaced with the component's name in the casing the token asks for, so a library that spells its files in kebab, camel or snake case is described as it is. A pattern with no token at all is a literal filename, index.ts is valid.

TokenAlertDialog becomes
{ComponentName}AlertDialog
{componentName}alertDialog
{component-name}alert-dialog
{component_name}alert_dialog

A component is reachable by any spelling, asking for AlertDialog and for alert-dialog resolves to the same files.

FieldDefaultDescription
filePattern{ComponentName}.tsxComponent source file pattern
storyPattern{ComponentName}.stories.tsxStorybook stories file pattern
stylePattern{ComponentName}.cssStylesheet file pattern
testPattern{ComponentName}.test.tsxTest file pattern
indexExportfalseWhether to generate an index.ts barrel export in the component directory

Describing a library you didn't scaffold

componentDir and the name tokens above exist so Spektral can read and edit an existing library where it already is. You do not move files, rename directories, or restructure a repo to adopt it: you describe the shape it already has.

Three shapes cover most real design systems:

On diskcomponentDirfilePattern
AlertDialog/AlertDialog.tsxomit{ComponentName}.tsx
alert-dialog/alert-dialog.tsx"{component-name}"{component-name}.tsx
alert-dialog/index.ts"{component-name}"index.ts
alert-dialog.tsx (flat)""{component-name}.tsx
paths:
  componentsDir: "src/components"
  componentDir: "{component-name}"

output:
  filePattern: "{component-name}.tsx"
  stylePattern: "{component-name}.module.css"
  storyPattern: "{component-name}.stories.tsx"

storybook:
  # Your library's own Storybook, run where it lives.
  cwd: "../../../packages/ui"

validation:
  # It is not your code. Do not have Spektral audit it as if it were.
  composition: false
  css: false

The name on disk is the name Spektral uses. Components are listed under exactly the spelling they have in your repo: never a normalised one. A name is not round-tripped back through a pattern to recover a "canonical" form, because that is lossy for acronyms and a lossy name is a component the app can write but not find again.

Deleting is scoped to the layout. Where a component owns a directory, deleting removes that directory. In a flat library it does not own one, so only the files your patterns attribute to that component are removed: shared helpers, barrels and files no pattern claims are left alone.

Existing projects are unaffected. Every default reproduces the previous behaviour exactly, down to the identifiers used for cloud sync. Leaving componentDir out changes nothing.

styling

FieldDefaultDescription
approachcss-variablesStyling strategy: "css-variables", "tailwind", "css-modules", "styled-components"
tokensFormatcss-custom-propertiesHow tokens are consumed
tokensImport:Import path for token stylesheet
shadowDomTokenImportfalseSet true for Shadow DOM frameworks (e.g., Lit) that need tokens injected into component styles

componentPattern

FieldDefaultDescription
exportStyle"named""named" or "default": how the component is exported
propsInterface:Props interface naming pattern (e.g., "{ComponentName}Props")
forwardReffalseWhether to use React.forwardRef
displayNamefalseWhether to set displayName on the component
customElementPrefix:Custom element prefix for web components (e.g., "ds-")

scaffold

FieldDefaultDescription
viteTemplatevanilla-tsThe npm create vite template name. Built-in platforms use framework-specific templates (react-ts, vue-ts, etc.). Custom platforms default to vanilla-ts.

storybook

FieldDefaultDescription
baseUrl"http://localhost:6006"URL where Storybook runs
frameworkPackage@storybook/html-viteStorybook framework integration package. Built-in platforms use framework-specific packages. Custom platforms default to html-vite.
cwdthe platform's ds-lib/Where Storybook runs, relative to ds-lib/. Point it at the directory holding .storybook/ when your library keeps its own Storybook somewhere else.
startCommandstorybook dev -p <port> --ciOverrides the command used to start Storybook. Run in cwd.

validation

Toggle individual audits the agent runs after generating or iterating on a component. Disabling an audit keeps edits surgical: useful for messy legacy codebases where broad auditing nudges the agent to clean up adjacent code instead of staying within the area you asked about. Disabled audits also remove the corresponding manual audit button from the UI.

FieldDefaultDescription
compositiontrue (React), false (others)Whether to run the composition validator (checks forwardRef, displayName, etc.). Only meaningful for React-like frameworks.
csstrueWhether to run validate_css: flags hardcoded colors and sizes that should reference design tokens. Set to false to skip the audit and keep agent edits surgical.

Note: the previous validation.accessibility toggle has been replaced by the new audits list. Declare a check_accessibility entry in audits to enable it, or omit it to disable. The old field is parsed but ignored for backwards compatibility.

composition

Tells the agent how to shape components that naturally have sub-parts (Tabs + Tab + TabPanel, Navigation + NavigationItem, Table + TableRow + TableCell, etc.). The agent decides whether to decompose; this field decides what the resulting file layout looks like.

FieldDefaultDescription
patternautoOne of siblings, compound, or auto. See below.
  • siblings: one folder per component. Navigation/, NavigationItem/, NavigationSection/ each get their own .tsx / .css / .stories.tsx / spec. Shared state moves into a small Context module the siblings import. Easiest to reuse later.
  • compound: a single folder whose component file exports all parts as named exports (Accordion-style). Use when children only ever appear inside the parent and depend on its Context.
  • auto: the agent picks per request. Defaults to siblings when the child can stand alone.
composition:
  pattern: siblings

scripts

Whitelist of shell commands the agent can run via the run_script tool without prompting the user. Anything the agent invokes that is not on this list still works: the agent loop pauses and an inline approval card appears in the UI so you can Allow once or Deny. Use this to authorize routine commands you've already approved (lint, build, test) so the agent doesn't interrupt you for them.

FieldDefaultDescription
id:Required. Short slug the agent passes to run_script (e.g. lint, build_tokens). Must be unique across scripts.
description:Optional. Short note for humans reviewing the file; not currently sent to the agent.
command:Required. Executable name or absolute path (e.g. npm, pnpm, node). Spawned without a shell; arguments are literal argv entries.
args[]Argument list passed to the command, e.g. ["run", "lint"].
cwdproject rootWorking directory, resolved relative to the project root. Paths that escape the project root are rejected.
timeout120000Milliseconds before the process is killed. Capped at 600000 (10 minutes).

Two ways to call run_script

  • Whitelisted (auto-runs). The agent passes id; the runner uses the declared command, args, and cwd. No prompt.
  • Free-form (gated). The agent passes command + optional args/cwd/reason. The agent pauses; you click Allow once or Deny in the inline approval card. Denied requests return as a tool error so the agent can adjust its approach.

Example

scripts:
  - id: lint
    description: Run ESLint on the React component library
    command: npm
    args: ["run", "lint"]
    cwd: platforms/react/ds-lib

  - id: build_tokens
    description: Rebuild design tokens (Style Dictionary)
    command: npm
    args: ["run", "build:tokens"]
    cwd: platforms/react/ds-lib

Disable entirely: add scripts under features.disabled to remove the run_script tool from the agent's tool list. The agent then can't ask to run anything.

audits

Custom checks the agent can run after writing a component. Each entry registers as its own named tool in the agent's tool list: the agent picks it directly from the description without an extra discovery step. Use this to expose platform-specific tests like an axe-core a11y audit, an iOS xcodebuild test run, an ESLint pass, or any shell command. Audits also appear as buttons in the manual audit panel beside the live preview.

Each audit has three required fields:

FieldTypeDescription
idstringTool name surfaced to the agent. Use snake_case. Must be unique across audits and not collide with built-in tool names. Example: check_accessibility, run_xcodebuild_tests.
descriptionstringWhat the audit does and when the agent should call it. The agent reads this verbatim, so be specific.
runnerobjectHow to execute the audit. See the runner types below.

runner.type: vitest-storybook

Runs npx vitest run --project <project> --reporter=json in the platform's ds-lib/, filtered to the component's stories file. Used by the React platform's built-in check_accessibility audit.

FieldRequiredDescription
projectYesVitest project name (passed to --project). Storybook's Vitest addon registers a project named storybook.
filterYesStory filename pattern, e.g. "{ComponentName}.stories.tsx". The {ComponentName} token is replaced with the PascalCase component name.
reportTypeNoWhich meta.reports entry to extract from the Vitest JSON. Set to a11y to get the structured axe-core violation report (rendered as a markdown summary). Omit to return the whole Vitest JSON.

runner.type: shell

Universal escape hatch: runs an arbitrary command. Stdout and stderr are returned to the agent verbatim alongside the exit code, so any check that prints actionable output works. Commands are spawned without a shell (no globbing or piping); arguments are passed as literal argv entries.

FieldDefaultDescription
command:Required. Executable name or absolute path (e.g. xcodebuild, npx).
args[]Argument list. Each entry may include the {ComponentName} template token, replaced before exec.
cwdproject rootWorking directory, resolved relative to the project root. Paths that escape the project root are rejected.
timeout60000Milliseconds before the process is killed.
parser"text""text" returns stdout/stderr/exitCode as text. "json" additionally tries to JSON.parse stdout and surfaces the parsed object to the agent.

Example

A platform that runs an a11y audit (built-in) and a shell-based test command:

audits:
  - id: check_accessibility
    description: Run axe-core via Storybook's Vitest addon against all stories for a component.
    runner:
      type: vitest-storybook
      project: storybook
      filter: "{ComponentName}.stories.tsx"
      reportType: a11y

  - id: run_xcodebuild_tests
    description: Build and test the iOS package for this component.
    runner:
      type: shell
      command: xcodebuild
      args: ["test", "-scheme", "DesignSystem", "-only-testing:DesignSystemTests/{ComponentName}Tests"]
      cwd: ios
      timeout: 300000

Full Example (React)

name: React
description: React 18+ components with TypeScript
language: typescript
framework: react
version: "18.x"

paths:
  componentsDir: "src/components"

output:
  filePattern: "{ComponentName}.tsx"
  storyPattern: "{ComponentName}.stories.tsx"
  stylePattern: "{ComponentName}.css"
  testPattern: "{ComponentName}.test.tsx"
  indexExport: true

styling:
  approach: css-variables
  tokensFormat: css-custom-properties

componentPattern:
  exportStyle: named
  propsInterface: "{ComponentName}Props"
  forwardRef: true
  displayName: true

scaffold:
  viteTemplate: react-ts

storybook:
  baseUrl: "http://localhost:6006"
  frameworkPackage: "@storybook/react-vite"

validation:
  composition: true
  css: true

composition:
  pattern: siblings

scripts:
  - id: lint
    description: Run ESLint
    command: npm
    args: ["run", "lint"]
    cwd: platforms/react/ds-lib
  - id: build_tokens
    description: Rebuild design tokens
    command: npm
    args: ["run", "build:tokens"]
    cwd: platforms/react/ds-lib

audits:
  - id: check_accessibility
    description: Run axe-core via Storybook's Vitest addon against all stories for a component.
    runner:
      type: vitest-storybook
      project: storybook
      filter: "{ComponentName}.stories.tsx"
      reportType: a11y

agentRules: |
  Framework rules (React):
  - Use forwardRef with displayName and named export.
  - Props interface named {ComponentName}Props extending HTMLAttributes.
  - Stories use @storybook/react with Meta/StoryObj.