Component contract
One file, one component. It records the component's identity, its display name, its typed props, its slots, and its states.
| Schema file | schemas/v0/component.contract.schema.json |
|---|---|
| Dialect | https://json-schema.org/draft/2020-12/schema |
$id | https://knapsack-oss.github.io/design-system-contract/schemas/v0/component.contract.schema.json (identifier only, not a network location; see schema identifiers) |
| Root type | object |
| Required | contractId, component |
| Additional members | Not permitted (additionalProperties: false) |
| Example file | examples/button-primary.contract.json |
The smallest valid component contract carries only the two required members.
{ "contractId": "button-primary", "component": "Button Primary" }
Field summary
| Member | Type | Required | Constraint |
|---|---|---|---|
$schema | string | No | minLength: 1 |
contractId | string | Yes | pattern ^[a-z0-9]+(-[a-z0-9]+)*$ |
component | string | Yes | minLength: 1 |
description | string | No | None |
cemRef | string | No | minLength: 1 |
shadcnRef | string | No | minLength: 1 |
props | object | No | See props |
slots | array of slot | No | Items: non-empty string or slot record |
states | array of string | No | Items: minLength: 1 |
The schema defines no schema-level default values. The only defaults in a contract are per-prop default values that the contract itself declares.
Root members
$schema #
Optional pointer to this schema. The URL is the schema's $id and is served from this site.
"$schema": "https://knapsack-oss.github.io/design-system-contract/schemas/v0/component.contract.schema.json"
contractId #
The contract's normative identity: the fold of the component's declared name. Lowercase letters and digits in hyphen-separated segments. Not namespaced and not versioned. Every valid contractId is also a valid DSDS (Design System Documentation Specification) component id. Unique within a manifest.
"contractId": "button-primary"
Invalid against the pattern
"contractId": "Button-Primary" // uppercase letter
"contractId": "button--primary" // empty segment between hyphens
"contractId": "-button" // leading hyphen
"contractId": "forms.button" // period
The schema checks the pattern only. It does not check that the value is the fold of any declared name, and it does not check uniqueness across a manifest.
component #
The design system's own display name for the component, carried verbatim. Never folded, and may differ from contractId. A publisher does not derive it from contractId and does not require it to equal contractId (SR-123).
"contractId": "button-primary",
"component": "Button Primary"
description #
Human-readable summary of the component.
"description": "The primary call-to-action button."
cemRef #
Custom Elements Manifest (CEM) identifier for this component. Carried as an identifier only; the contract does not restate the shape of the CEM entry.
Illustrative value"cemRef": "ds-button-primary"
shadcnRef #
shadcn registry item identifier for this component. Carried as an identifier only; the contract does not restate the shape of the registry item.
Illustrative value"shadcnRef": "button"
Any other root member #
A root member not listed on this page fails schema validation. The format has no variants member (SR-128); variant axes are enum-typed props.
{
"contractId": "button-primary",
"component": "Button Primary",
"variants": { "variant": ["primary", "secondary"] }
}
Props
props #
The component's props, written as a JSON Schema 2020-12 object schema. A stock JSON Schema validator can check a prop bag against it directly. When props is present, additionalProperties is required and states whether undeclared props are allowed.
"props": {
"properties": {
"label": { "type": "string", "description": "Visible text" },
"variant": { "type": "string", "enum": ["primary", "secondary"], "default": "primary" },
"disabled": { "type": "boolean", "default": false }
},
"required": ["label"],
"additionalProperties": false
}
props.properties #
Map from prop name to that prop's schema. The schema places no pattern on prop names; "" is a legal key.
"properties": {
"label": { "type": "string" },
"size": { "type": "string", "enum": ["sm", "md", "lg"], "default": "md" }
}
props.required #
Names of props a usage must supply. Every name must be a key of props.properties. The schema cannot check this; a publisher checks it at publish and fails the publish naming the prop (SR-126).
"required": ["label"]
Fails schema validation (duplicate item)
"required": ["label", "label"]
Passes schema validation, fails at publish (icon is not a key of properties)
"properties": { "label": { "type": "string" } },
"required": ["icon"]
props.additionalProperties #
| Value | Meaning |
|---|---|
false | A prop outside properties is undeclared (closed). |
true | Any prop is permitted (open). |
additionalProperties missing)
"props": { "properties": { "label": { "type": "string" } } }
Each of these declares that the component takes no props. All three are schema-equivalent.
- Omit
propsentirely. "props": { "additionalProperties": false }(nopropertieskey)."props": { "properties": {}, "additionalProperties": false }
"props": { "additionalProperties": true } with no properties declares an empty prop set that is open: no declared props, any prop permitted (SR-048).
Prop schema
Prop ($defs/prop) #
One prop's schema. If both enum and default are present, default must be one of the enum values. The schema cannot check this; a publisher checks it at publish and fails the publish naming the prop (SR-127).
"variant": { "type": "string", "enum": ["primary", "secondary"], "default": "primary" }
Fails validation (keyword outside the permitted four)
"count": { "type": "integer", "minimum": 0 }
type #
A JSON Schema 2020-12 type name, or an array of them.
Valid"type": "string"
"type": ["string", "null"]
Invalid
"type": "float" // not a type name
"type": [] // minItems: 1
"type": ["string", "string"] // uniqueItems
Type names ($defs/typeName) #
Exactly the seven JSON Schema 2020-12 primitive type names:
string, number, integer, boolean, array, object, null
A prop typed array or object carries no items or properties; those keywords are not permitted on a prop in v0.1.
default #
The value used when a usage does not supply this prop.
"disabled": { "type": "boolean", "default": false }
Passes schema validation, fails at publish
"variant": { "type": "string", "enum": ["primary", "secondary"], "default": "tertiary" }
description #
Human-readable summary of the prop.
"label": { "type": "string", "description": "Visible text" }
enum #
The allowed values. A prop carrying enum is a variant axis.
"size": { "type": "string", "enum": ["sm", "md", "lg"], "default": "md" }
"enum": [] validates against the v0.1 schema. The schema also does not check that enum values match the prop's type.
Variant axes #
Any prop whose schema carries enum is a variant axis, whatever its name. A prop named variant has no special status. The format declares no separate variants member (SR-128).
"properties": {
"variant": { "type": "string", "enum": ["primary", "secondary"], "default": "primary" },
"tone": { "type": "string", "enum": ["neutral", "brand"] }
}
This example declares two variant axes: variant and tone.
Slots and states
A slots item is a bare name or a record that can carry description and a11y. A states item is a plain string only. v0.1 has no per-state metadata.
slots #
Named insertion points the component exposes. How a publisher obtains slot names is not specified.
"slots": [
"icon",
{ "name": "body", "a11y": { "nameFrom": "content", "required": true } }
]
Slot record ($defs/slotRecord) #
| Member | Type | Required | Constraint |
|---|---|---|---|
name | string | Yes | minLength: 1 |
description | string | No | None |
a11y | object | No | Slot accessibility |
{ "name": "body", "description": "Main content", "a11y": { "nameFrom": "content", "required": true } }
Slot accessibility ($defs/slotAccessibility) #
Accessibility requirements for a slot. Role and naming vocabulary are those of ARIA (Accessible Rich Internet Applications). The schema accepts any non-empty string for role and nameFrom; it does not restrict values to the ARIA vocabulary.
| Member | Type | Required | Constraint |
|---|---|---|---|
role | string | No | minLength: 1 |
nameFrom | string | No | minLength: 1 |
required | boolean | No | Not defined in v0.1. The schema declares it but the specification doesn't yet say what it requires. See known limitations. |
"a11y": { "nameFrom": "content", "required": true }
states #
Named conditions the component can be in. How a publisher obtains state names is not specified.
"states": ["hover", "disabled"]
Fails validation (states carry no records)
"states": [{ "name": "hover" }]
Rules the schema does not check
A JSON Schema validator that accepts a component contract has not checked the following. Each is a publisher obligation in the specification. This repository ships no checker tool for them.
| Rule | Checked by | Spec |
|---|---|---|
contractId is the fold of the declared name | Publisher | SR-130 |
contractId is unique within the manifest | Publisher | SR-133 |
Every props.required name is a key of props.properties | Publisher | SR-126 |
default is one of the enum values when both are present | Publisher | SR-127 |
Complete example
Source: examples/button-primary.contract.json
{
"$schema": "https://knapsack-oss.github.io/design-system-contract/schemas/v0/component.contract.schema.json",
"contractId": "button-primary",
"component": "Button Primary",
"description": "The primary call-to-action button.",
"props": {
"properties": {
"label": { "type": "string", "description": "Visible text" },
"variant": { "type": "string", "enum": ["primary", "secondary"], "default": "primary" },
"disabled": { "type": "boolean", "default": false }
},
"required": ["label"],
"additionalProperties": false
},
"slots": [
"icon",
{ "name": "body", "a11y": { "nameFrom": "content", "required": true } }
],
"states": ["hover", "disabled"]
}