{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://designsystemdocspec.org/v0.15.2/document-blocks/variants.schema.json",
"title": "Variants document block",
"description": "Every way a component can be configured — a toggle (flag variant, like 'disabled') or a set of options (enum variant, like size: sm | md | lg). Each can explain why it exists.",
"$defs": {
"variantValue": {
"type": "object",
"description": "A single option within an enum variant dimension.",
"required": [
"identifier",
"description"
],
"properties": {
"identifier": {
"type": "string",
"description": "Machine-readable value identifier (ex: 'sm', 'md', 'lg', 'primary', 'secondary')."
},
"name": {
"type": "string",
"description": "Human-readable name (ex: 'Small', 'Medium', 'Large', 'Primary', 'Secondary')."
},
"description": {
"$ref": "../common/rich-text.schema.json#/$defs/richText",
"description": "What this value looks like, when to use it, and how it differs from other values in this dimension."
},
"rationale": {
"$ref": "../common/rich-text.schema.json#/$defs/richText",
"description": "Why this value exists (ex: 'High emphasis directs users to the one key action'). For guidance comparing values, use `use-cases` instead."
},
"examples": {
"type": "array",
"description": "Examples showing this variant value in isolation or in context.",
"items": {
"$ref": "../common/example.schema.json#/$defs/example"
},
"minItems": 1
},
"tokens": {
"$ref": "../common/token-overrides.schema.json#/$defs/tokenOverrides",
"description": "Token overrides applied when this value is selected."
}
},
"additionalProperties": false
},
"flagVariant": {
"type": "object",
"description": "A toggle — on or off. Use it for binary capabilities like 'disabled' or 'full-width'. No `values` array; it's simply active or not.",
"required": [
"kind",
"identifier",
"description"
],
"properties": {
"kind": {
"type": "string",
"const": "flag",
"description": "Identifies this variant as a boolean flag."
},
"identifier": {
"type": "string",
"description": "Machine-readable identifier of the flag (ex: 'disabled', 'full-width', 'icon-only', 'loading')."
},
"name": {
"type": "string",
"description": "Human-readable name of the flag (ex: 'Disabled', 'Full Width', 'Icon Only', 'Loading')."
},
"description": {
"$ref": "../common/rich-text.schema.json#/$defs/richText",
"description": "What this flag controls and how it affects the component's appearance or behavior when active."
},
"rationale": {
"$ref": "../common/rich-text.schema.json#/$defs/richText",
"description": "Why this flag exists (ex: 'Prevents interaction during submission to avoid duplicate requests')."
},
"examples": {
"type": "array",
"description": "Examples showing the component with this flag active.",
"items": {
"$ref": "../common/example.schema.json#/$defs/example"
},
"minItems": 1
},
"tokens": {
"$ref": "../common/token-overrides.schema.json#/$defs/tokenOverrides",
"description": "Token overrides applied when this flag is active."
}
},
"additionalProperties": false
},
"enumVariant": {
"type": "object",
"description": "A set of options where you pick one, like 'size' (sm | md | lg) or 'emphasis' (primary | secondary | ghost).",
"required": [
"kind",
"identifier",
"description",
"values"
],
"properties": {
"kind": {
"type": "string",
"const": "enum",
"description": "Identifies this variant as an enumerated set of values."
},
"identifier": {
"type": "string",
"description": "Machine-readable name for this dimension (ex: 'size', 'emphasis') — the axis, not one of its values."
},
"name": {
"type": "string",
"description": "Human-readable name of the variant dimension (ex: 'Size', 'Emphasis', 'Shape')."
},
"description": {
"$ref": "../common/rich-text.schema.json#/$defs/richText",
"description": "What this dimension of variation controls and how its values affect the component's appearance or behavior."
},
"values": {
"type": "array",
"description": "The possible values. A single value is fine — dimensions often start with just one while more are added over time. Tools SHOULD keep this order.",
"items": {
"$ref": "#/$defs/variantValue"
},
"minItems": 1
}
},
"additionalProperties": false
},
"variants": {
"type": "object",
"description": "Every way a component or pattern can be configured — a toggle or a set of options. Multiple items combine independently. A variant is a choice the consumer makes up front (ex: 'size', 'full-width'); a condition entered at runtime (hover, disabled, loading) is a state — document that in `states` instead.",
"required": [
"kind",
"items"
],
"properties": {
"kind": {
"type": "string",
"const": "variants",
"description": "Identifies this block as a variants spec."
},
"items": {
"type": "array",
"description": "The variant dimensions, in order. Tools SHOULD keep this order.",
"items": {
"type": "object",
"description": "One variant dimension. `kind: 'flag'` makes it a toggle; `kind: 'enum'` makes it a set of options. We use `if`/`then` instead of `oneOf` so an invalid entry gives one clear error instead of many.",
"required": [
"kind"
],
"properties": {
"kind": {
"enum": [
"flag",
"enum"
]
}
},
"allOf": [
{
"if": {
"required": [
"kind"
],
"properties": {
"kind": {
"const": "flag"
}
}
},
"then": {
"$ref": "#/$defs/flagVariant"
}
},
{
"if": {
"required": [
"kind"
],
"properties": {
"kind": {
"const": "enum"
}
}
},
"then": {
"$ref": "#/$defs/enumVariant"
}
}
]
},
"minItems": 1
},
"$extensions": {
"$ref": "../common/extensions.schema.json#/$defs/extensions"
}
},
"additionalProperties": false
}
}
}
Identifies this block as a variants spec.The variant dimensions, in order. Tools SHOULD keep this order. Min items: 1All vendor-specific extensions . Keys MUST use a namespace of at least two dot-separated segments (reverse domain recommended), Example: 'com.figma', 'acme.tooling'; the pattern is case-tolerant. Tools that don't recognize an extension MUST keep it. Extension data SHOULD NOT duplicate core schema fields.References:flagVariant, enumVariant, extensions{
"kind": "variants",
"items": [
{
"kind": "enum",
"identifier": "emphasis",
"name": "Emphasis",
"description": "Controls the visual weight of the button to establish a visual hierarchy among actions on a surface.",
"values": [
{
"identifier": "primary",
"description": "High-emphasis — the main action on the surface."
},
{
"identifier": "secondary",
"description": "Medium-emphasis — important but not primary."
},
{
"identifier": "ghost",
"description": "Low-emphasis — tertiary actions and dense layouts."
},
{
"identifier": "danger",
"description": "Signals a destructive or irreversible action."
}
]
},
{
"kind": "enum",
"identifier": "size",
"name": "Size",
"description": "Controls the physical dimensions and internal padding of the button.",
"values": [
{
"identifier": "sm",
"name": "Small",
"description": "Compact size for toolbars, table rows, and dense layouts. 32px height."
},
{
"identifier": "md",
"name": "Medium",
"description": "Default size for most contexts. 40px height."
},
{
"identifier": "lg",
"name": "Large",
"description": "Touch-optimized size for mobile-first surfaces. 48px height."
}
]
},
{
"kind": "flag",
"identifier": "full-width",
"name": "Full Width",
"description": "The button stretches to fill the full width of its parent container."
},
{
"kind": "flag",
"identifier": "icon-only",
"name": "Icon Only",
"description": "The button renders a single icon with no visible label. An aria-label is required."
}
]
}Identifies this variant as a boolean flag.Machine-readable identifier of the flag (ex: 'disabled', 'full-width', 'icon-only', 'loading').What this flag controls and how it affects the component's appearance or behavior when active.Human-readable name of the flag (ex: 'Disabled', 'Full Width', 'Icon Only', 'Loading').Why this flag exists (ex: 'Prevents interaction during submission to avoid duplicate requests').Examples showing the component with this flag active. Min items: 1Token overrides applied when this flag is active.References:richText, example, tokenOverrides[
{
"kind": "flag",
"identifier": "full-width",
"name": "Full Width",
"description": "When active, the button stretches to fill the full width of its parent container. Label text is centered.",
"rationale": "- Use when: When the button is the sole action in a narrow container such as a mobile sheet, a card footer, or a single-column form.\n- Avoid when: When multiple buttons appear side by side in a button group or dialog footer. Use `default width` instead — Full-width buttons in a horizontal group force a stacked layout, which breaks the expected left-to-right reading order for action groups."
},
{
"kind": "flag",
"identifier": "icon-only",
"name": "Icon Only",
"description": "When active, the button renders a single icon with no visible label. An aria-label is required."
}
]Identifies this variant as an enumerated set of values.Machine-readable name for this dimension (ex: 'size', 'emphasis') — the axis, not one of its values.What this dimension of variation controls and how its values affect the component's appearance or behavior.The possible values. A single value is fine — dimensions often start with just one while more are added over time. Tools SHOULD keep this order. Min items: 1Human-readable name of the variant dimension (ex: 'Size', 'Emphasis', 'Shape').References:richText, variantValue[
{
"kind": "enum",
"identifier": "emphasis",
"name": "Emphasis",
"description": "Controls the visual weight of the button. Determines background fill, border treatment, and text color to establish a visual hierarchy among actions on a surface.",
"values": [
{
"identifier": "primary",
"description": "High-emphasis — the main action on the surface."
},
{
"identifier": "secondary",
"description": "Medium-emphasis — important but not primary."
},
{
"identifier": "ghost",
"description": "Low-emphasis — tertiary actions and dense layouts."
},
{
"identifier": "danger",
"description": "Signals a destructive or irreversible action."
}
]
},
{
"kind": "enum",
"identifier": "size",
"name": "Size",
"description": "Controls the physical dimensions and internal padding of the button. All sizes maintain a minimum 44×44 CSS pixel tap target.",
"values": [
{
"identifier": "small",
"description": "Compact size for toolbars, table rows, and dense layouts."
},
{
"identifier": "medium",
"description": "The default size. Suitable for most contexts including forms, dialogs, and page-level actions."
},
{
"identifier": "large",
"description": "Touch-optimized size for mobile-first surfaces and marketing pages."
}
]
}
]Machine-readable value identifier (ex: 'sm', 'md', 'lg', 'primary', 'secondary').What this value looks like, when to use it, and how it differs from other values in this dimension.Human-readable name (ex: 'Small', 'Medium', 'Large', 'Primary', 'Secondary').Why this value exists (ex: 'High emphasis directs users to the one key action'). For guidance comparing values, use use-cases instead.Examples showing this variant value in isolation or in context. Min items: 1Token overrides applied when this value is selected.References:richText, example, tokenOverrides[
{
"identifier": "primary",
"name": "Primary",
"description": "High-emphasis — the main action on the surface. Uses a solid, filled background. Limit to one primary button per surface.",
"rationale": "- Use when: When the action is the most important on the surface — the one the user is most likely to take (ex: Save, Submit, Confirm).\n- Avoid when: When a surface already has a primary button. Adding a second dilutes visual hierarchy. Use `secondary` instead — Secondary emphasis maintains importance without competing with the existing primary action."
},
{
"identifier": "secondary",
"name": "Secondary",
"description": "Medium-emphasis — important but not the primary action. Uses a visible border and transparent background."
},
{
"identifier": "ghost",
"name": "Ghost",
"description": "Low-emphasis — for tertiary actions, toolbar actions, or dense layouts where visual weight must be minimized."
},
{
"identifier": "danger",
"name": "Danger",
"description": "Signals a destructive or irreversible action. Use for delete, remove, or disconnect actions.",
"rationale": "- Use when: When the action is destructive or irreversible — deleting a record, revoking access, removing a team member.\n- Avoid when: When the action is not destructive, even if it feels important or urgent. Use `primary` instead — The danger color is a strong signal reserved for destruction. Using it for non-destructive actions dilutes its meaning."
}
]