Specify Design Token Format

Learn more about the Specify Design Token Format (SDTF) specifications.

Introduction

The Specify Design Token Format (SDTF) is a JSON based notation made to capture the architecture of information of design tokens regardless of its source. Supporting 50+ token types, the SDTF allows to compose the most complex types by referencing/aliasing others. Its primary focus is compatibility among design (system) tools and the frontend technologies used to build design systems.

In Specify, each repository is home to one SDTF token tree.

The SDTF is a transport format, if it can be read easily, we do not recommend writing its content directly. Instead, use the APIs provided by Specify.

JSON Tree Structure

With the SDTF, you organize design tokens as a regular JSON tree with some remarkable signatures such as token, group and collection.

With groups and collections, you create sets of tokens to convey meaning like color.primary where color is the group and where my primary is a token, nested in color.

There is no technical limitation to the level of nesting you can achieve, yet good practices would drive the usage to a couple or so.

Example with dimensions in a group
const dimensionTokens: SpecifyDesignTokenFormat = {
  spacing: { // spacing is a Group
    one: { // one is a Token
      $type: 'spacing',
      $value: {
        default: { unit: 'px', value: 4 }
      }
    },
    two: { // two is a Token
      $type: 'spacing',
      $value: {
        default: { unit: 'px', value: 8 }
      }
    }
  }
}
Example with colors in a collection
const tokens: SpecifyDesignTokenFormat = {
  theme: {
    $collection: { $modes: ['light', 'dark'] },
    color: {
      blue: {
        $type: 'color',
        $value: {
          light: { model: 'hex', hex: '#1919C4', alpha: 1 },
          dark: { model: 'hex', hex: '#111146', alpha: 1 }
        }
      }
    }
  }
}

Tree node (token, group, collection)

A tree node is any of token, group or collection that live within a SDTF token tree.

Naming

Token, group and collection names are free to use any character except the following:

  • . (dot/period) used for aliasing

  • $ reserved for SDTF

Common properties

Any group, collection or design token can hold extra properties:

  • $description a string aimed to complement the name.

  • $extensions a JSON object to complement the value for a given provider.

Example with dimensions
const dimensionTokens: SpecifyDesignTokenFormat = {
  spacing: {
    $description: "Spacings for the webapp",
    one: {
      $description: "default spacing",
      $type: "spacing",
      $value: {
        default: { unit: "px", value: 4 },
      },
    },
    two: {
      $description: "doubled spacing",
      $type: "spacing",
      $value: {
        default: { unit: "px", value: 8 },
      },
      $extensions: { "com.specifyapp.highlighted": true },
    },
  },
};

Note on $extensions

The key used to index the $extensions object in $extensions: { [key: string]: JSONValue }MUST follow the reverse domain name notation in order to ensure collision-free collaboration of actors.

Tree path

The tree path is the unique identifier of a tree node within the token tree. It is formed from the enumeration of the node's parent names.

In the following example, the token base is nested into the spacing group. Thus, its path is ['spacing', 'base'] or "spacing.base".

const dimensionTokens: SpecifyDesignTokenFormat = {
  spacing: {
    base: {
      $type: "spacing",
      $value: { default: { unit: "px", value: 4 }},
    },
  },
};

Representations

Among the Specify APIs, you'll find 2 path representations.

  • the string based representation concatenates the path with . (dot/period) . Mainly used for aliasing. Giving for our previous example "spacing.base".

  • the array based representation with an array of string, where each item is the name of a tree node. Mainly used for working with the SDTF Engine. Giving for our previous example ['spacing', 'base'].

Design token

A design token represents the value of a design decision for all the possible modes you want to attach. It is defined by its $type and its corresponding $value, where the $value is a JSON object, Record<mode: string, value: any>, where the mode name is the key and holds to the actual value.

A design token can be placed anywhere within a group or a collection.

const dimensionToken = {
  $type: 'dimension', // Mandatory
  $value: { // Mandatory
    default: { // Stands for "default mode", mode name can be any string not starting with "$"
      value: 1, unit: '%' // Must be a valid value or reference for the type
    }
  },
  $description: 'A dimension token', // Optional string
  $extensions: { 'com.specifyapp.data': true } // Optional Record<string, JSONValue>
}

Token value

Value defines at least one mode

Any Token Value MUST define at least one mode.

Default mode, by convention, is: default such as $value: { default: ... }

Modes naming

Modes names are free to use any character except the following:

  • . (dot/period) used for aliasing

  • $ reserved for SDTF

Modes outside a collection

A token placed outside of a collection can define any mode as far as it defines at least one mode.

Modes inside a collection

A token placed inside of a collection must strictly define the modes allowed by the collection.

Aliasing (reference)

Within a design token value, we can declare { $alias: 'path.to.token' } to reference another token, thus avoiding information duplication.

You can alias tokens through three levels.

Top-level aliasing

You can alias an entire Token. Hence you also inherit from its modes.

aDimension: {
  $type: 'dimension',
  $value: { $alias: 'a.reference.to.a.dimension' },
}

Note that $mode does not apply here.

Mode-level aliasing

You can alias on one or many modes of a Token. The value resolution will follow the indicated target $mode.

aDimension: {
  $type: 'dimension',
  $value: {
    small: { $alias: 'a.reference.to.a.small.dimension', $mode: 'customMode' }, // this is the mode level alias
    large: { value: 1, unit: 'px' },
  },
}

Note that $mode is mandatory

Value-level aliasing

You can alias on (almost) all sub-values composing the SDTF. The value resolution will follow the indicated target $mode.

aDimension: {
  $type: 'dimension',
  $value: {
    default: {
      value: { $alias: 'a.reference.to.a.number', $mode: 'small' },
      unit: { $alias: 'a.reference.to.a.dimensionUnit', $mode: 'web' }
    },
  },
}

Note that $mode is mandatory

Aliasing validation

Aliases must reference compatible Tokens. Each type has a unique mapping to other types defining which ones can be referenced at which place.

Example of invalid aliasing

Here is a dimension using value level aliasing.

aDimension: {
  $type: 'dimension',
  $value: {
    default: {
      value: { $alias: 'a.value', $mode: "default" },
      unit: { $alias: 'a.unit', $mode: "default" },
    },
  },
},
  1. The Token expected to resolve for { $alias: 'a.value' } must be a number.

  2. But since number is a broad definition, we would also accept integerNumber, zeroToOneNumber, arcDegreeNumber, rgbColorNumber, positiveNumber, positiveIntegerNumber.

  3. Following the rationale, the Token expected to resolve for { $alias: 'a.unit' } must be a dimensionUnit.

Supported types

The SDTF defines a large variety of token types in order to capture (almost) any design decision.

  • bitmap

  • blur

  • border

  • breakpoint

  • color

  • dimension

  • duration

  • font

  • gradient

  • gradients

  • opacity

  • radius

  • radii

  • shadow

  • shadows

  • spacing

  • spacings

  • textStyle

  • transition

  • vector

  • zIndex

UI type helpers
  • bitmapFormat

  • borderStyle

  • borderStyleLineCap

  • cubicBezier

  • dimensionUnit

  • durationUnit

  • fontFamily

  • fontFeature

  • fontFeatures

  • fontFormat

  • fontStyle

  • fontWeight

  • shadowType

  • stepsTimingFunction

  • textAlignHorizontal

  • textAlignVertical

  • textDecoration

  • textTransform

  • vectorFormat

JSON inherited types
  • string

  • number

  • boolean

  • null

  • array

  • object

Constrained primitives
  • integerNumber

  • zeroToOneNumber

  • arcDegreeNumber

  • rgbColorNumber

  • positiveNumber

  • positiveIntegerNumber

  • percentageNumber

  • hexadecimalColorString

Group

A group is meant to order and nest design tokens semantically, it can be placed in any parent group or collection.

A group is a simple JSON object that can contain none to many groups, collections or tokens. With no mandatory keys or children, a group can be empty and represented as an empty object {}.

const tokens = {
  spacing: { // Group name
    $description: 'All spacings for webapp', // Optional string
    $extensions: { 'com.specifyapp.data': true } // Optional Record<string, JSONValue>
    base: {
      // ... token data
    }
  }
}

Collection

A collection is a special kind of group defining a mandatory $collection property. It constrain any enclosed token to respect a given set of modes.

A Collection can be placed within any group, nested at any level. But a Collection cannot be nested within another Collection.

color: {
  $collection: { $modes: ['light', 'dark'] }, // $collection indicates this group is a collection
  black: {
    $type: 'color',
    $value: {
      light: { model: 'hex', hex: '#000000', alpha: 1 },
      dark: { model: 'hex', hex: '#000000', alpha: 1 },
    }
  },
  aGroup: { $description: 'This group is empty but shows it is allowed here' }
}

$collection property

The $collection property is an object strictly containing the $modes key.

The $modes property is an array of mode names (string) defining the allowed modes for any token within the collection.

Last updated