Every platform is defined by a single platform.yml file. This page documents every field, its type, default value, and purpose.
Top-Level Fields
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Display name shown in the UI (e.g., "React", "Static HTML") |
| description | string | No | Short description of the platform |
| language | string | No | Programming language (e.g., "typescript", "javascript") |
| framework | string | Yes | Framework identifier. Built-in: "react", "vue", "lit", "svelte". Custom platforms use any string. |
| version | string | No | Framework version for documentation (e.g., "18.x") |
| agentRules | string (multiline) | No | Framework-specific instructions for the AI agent. Injected into system prompts. If omitted, falls back to built-in rules for known frameworks. |
| indexExportTemplate | string | No | Template for barrel export files. Placeholders: {ComponentName}, {componentFile}, {baseNoExt}. |
paths
| Field | Default | Description |
|---|---|---|
| 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/ |
| componentDir | the component's name | How 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.
| Token | AlertDialog 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.
| Field | Default | Description |
|---|---|---|
| filePattern | {ComponentName}.tsx | Component source file pattern |
| storyPattern | {ComponentName}.stories.tsx | Storybook stories file pattern |
| stylePattern | {ComponentName}.css | Stylesheet file pattern |
| testPattern | {ComponentName}.test.tsx | Test file pattern |
| indexExport | false | Whether 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 disk | componentDir | filePattern |
|---|---|---|
| AlertDialog/AlertDialog.tsx | omit | {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
| Field | Default | Description |
|---|---|---|
| approach | css-variables | Styling strategy: "css-variables", "tailwind", "css-modules", "styled-components" |
| tokensFormat | css-custom-properties | How tokens are consumed |
| tokensImport | : | Import path for token stylesheet |
| shadowDomTokenImport | false | Set true for Shadow DOM frameworks (e.g., Lit) that need tokens injected into component styles |
componentPattern
| Field | Default | Description |
|---|---|---|
| exportStyle | "named" | "named" or "default": how the component is exported |
| propsInterface | : | Props interface naming pattern (e.g., "{ComponentName}Props") |
| forwardRef | false | Whether to use React.forwardRef |
| displayName | false | Whether to set displayName on the component |
| customElementPrefix | : | Custom element prefix for web components (e.g., "ds-") |
scaffold
| Field | Default | Description |
|---|---|---|
| viteTemplate | vanilla-ts | The npm create vite template name. Built-in platforms use framework-specific templates (react-ts, vue-ts, etc.). Custom platforms default to vanilla-ts. |
storybook
| Field | Default | Description |
|---|---|---|
| baseUrl | "http://localhost:6006" | URL where Storybook runs |
| frameworkPackage | @storybook/html-vite | Storybook framework integration package. Built-in platforms use framework-specific packages. Custom platforms default to html-vite. |
| cwd | the 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. |
| startCommand | storybook dev -p <port> --ci | Overrides 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.
| Field | Default | Description |
|---|---|---|
| composition | true (React), false (others) | Whether to run the composition validator (checks forwardRef, displayName, etc.). Only meaningful for React-like frameworks. |
| css | true | Whether 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.
| Field | Default | Description |
|---|---|---|
| pattern | auto | One 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 tosiblingswhen 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.
| Field | Default | Description |
|---|---|---|
| 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"]. |
| cwd | project root | Working directory, resolved relative to the project root. Paths that escape the project root are rejected. |
| timeout | 120000 | Milliseconds 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 declaredcommand,args, andcwd. No prompt. - Free-form (gated). The agent passes
command+ optionalargs/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:
| Field | Type | Description |
|---|---|---|
| id | string | Tool 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. |
| description | string | What the audit does and when the agent should call it. The agent reads this verbatim, so be specific. |
| runner | object | How 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.
| Field | Required | Description |
|---|---|---|
| project | Yes | Vitest project name (passed to --project). Storybook's Vitest addon registers a project named storybook. |
| filter | Yes | Story filename pattern, e.g. "{ComponentName}.stories.tsx". The {ComponentName} token is replaced with the PascalCase component name. |
| reportType | No | Which 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.
| Field | Default | Description |
|---|---|---|
| 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. |
| cwd | project root | Working directory, resolved relative to the project root. Paths that escape the project root are rejected. |
| timeout | 60000 | Milliseconds 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.