Component contract

One file, one component. It records the component's identity, its display name, its typed props, its slots, and its states.

Schema fileschemas/v0/component.contract.schema.json
Dialecthttps://json-schema.org/draft/2020-12/schema
$idhttps://knapsack-oss.github.io/design-system-contract/schemas/v0/component.contract.schema.json (identifier only, not a network location; see schema identifiers)
Root typeobject
RequiredcontractId, component
Additional membersNot permitted (additionalProperties: false)
Example fileexamples/button-primary.contract.json

The smallest valid component contract carries only the two required members.

{ "contractId": "button-primary", "component": "Button Primary" }

Field summary

MemberTypeRequiredConstraint
$schemastringNominLength: 1
contractIdstringYespattern ^[a-z0-9]+(-[a-z0-9]+)*$
componentstringYesminLength: 1
descriptionstringNoNone
cemRefstringNominLength: 1
shadcnRefstringNominLength: 1
propsobjectNoSee props
slotsarray of slotNoItems: non-empty string or slot record
statesarray of stringNoItems: 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 #

Path
/$schema
Type
string
Required
Optional
Constraints
minLength: 1
Default
None

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 #

Path
/contractId
Type
string
Required
Required
Constraints
pattern ^[a-z0-9]+(-[a-z0-9]+)*$
Default
None

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.

Valid
"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 #

Path
/component
Type
string
Required
Required
Constraints
minLength: 1
Default
None

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 #

Path
/description
Type
string
Required
Optional
Constraints
None. An empty string is valid.
Default
None

Human-readable summary of the component.

"description": "The primary call-to-action button."

cemRef #

Path
/cemRef
Type
string
Required
Optional
Constraints
minLength: 1. No format constraint on the identifier.
Default
None

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 #

Path
/shadcnRef
Type
string
Required
Optional
Constraints
minLength: 1. No format constraint on the identifier.
Default
None

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 #

Rule
additionalProperties: false at the root

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.

Fails validation
{
  "contractId": "button-primary",
  "component": "Button Primary",
  "variants": { "variant": ["primary", "secondary"] }
}

Props

props #

Path
/props
Type
object ($defs/props)
Required
Optional
Members
properties, required, additionalProperties (required). No other member is permitted.
Default
When omitted, the component declares an empty, closed prop set.

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 #

Path
/props/properties
Type
object; every value is a prop schema
Required
Optional
Constraints
No constraint on key names. An empty object is valid.
Default
None. When omitted or empty, the prop set is empty and its openness is props.additionalProperties (SR-048).

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 #

Path
/props/required
Type
array of string
Required
Optional
Constraints
uniqueItems: true
Default
None

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 #

Path
/props/additionalProperties
Type
boolean
Required
Required when props is present
Constraints
Boolean only. A schema object is not permitted.
Default
None
ValueMeaning
falseA prop outside properties is undeclared (closed).
trueAny prop is permitted (open).
Fails validation (additionalProperties missing)
"props": { "properties": { "label": { "type": "string" } } }
Empty prop set: three equivalent forms

Each of these declares that the component takes no props. All three are schema-equivalent.

  1. Omit props entirely.
  2. "props": { "additionalProperties": false } (no properties key).
  3. "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) #

Path
/props/properties/{name}
Type
object
Required members
type
Permitted members
type, default, description, enum. No other member (additionalProperties: false).

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 #

Path
/props/properties/{name}/type
Type
A type name string, or an array of type names
Required
Required
Constraints
Array form: minItems: 1, uniqueItems: true
Default
None

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 #

Path
/props/properties/{name}/default
Type
Any JSON value
Required
Optional
Constraints
None in the schema. If enum is present, must be one of its values (publisher-checked, SR-127).

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 #

Path
/props/properties/{name}/description
Type
string
Required
Optional
Constraints
None

Human-readable summary of the prop.

"label": { "type": "string", "description": "Visible text" }

enum #

Path
/props/properties/{name}/enum
Type
array of any JSON values
Required
Optional
Constraints
None beyond type: array. No minItems and no uniqueItems.

The allowed values. A prop carrying enum is a variant axis.

"size": { "type": "string", "enum": ["sm", "md", "lg"], "default": "md" }
Current schema behavior

"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

Slots and states are not symmetric

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 #

Path
/slots
Type
array; each item is a non-empty string or a slot record (oneOf)
Required
Optional
Constraints
String items: minLength: 1. No uniqueItems.
Default
None

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) #

Path
/slots/{index}
Type
object
Required members
name
Permitted members
name, description, a11y. No other member.
MemberTypeRequiredConstraint
namestringYesminLength: 1
descriptionstringNoNone
a11yobjectNoSlot accessibility
{ "name": "body", "description": "Main content", "a11y": { "nameFrom": "content", "required": true } }

Slot accessibility ($defs/slotAccessibility) #

Path
/slots/{index}/a11y
Type
object
Required members
None
Permitted members
role, nameFrom, required. No other member.

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.

MemberTypeRequiredConstraint
rolestringNominLength: 1
nameFromstringNominLength: 1
requiredbooleanNoNot 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 #

Path
/states
Type
array of string
Required
Optional
Constraints
Items: minLength: 1. No uniqueItems. Object items are not permitted.
Default
None

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.

RuleChecked bySpec
contractId is the fold of the declared namePublisherSR-130
contractId is unique within the manifestPublisherSR-133
Every props.required name is a key of props.propertiesPublisherSR-126
default is one of the enum values when both are presentPublisherSR-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"]
}