Design Identity

The .dna/identity.yaml file is the foundation of everything DNA does. It records what your codebase believes about design — palette, type scale, spacing grid — with per-rule provenance: who held authority and when.

What gets recorded

palette:
  brand:
    value: "hsl(38 47% 48%)"
    from: code
    at: 2026-08-10
  foreground:
    value: "#18181C"
    from: figma
    at: 2026-07-31
    reason: "reconciled to the committed frames"

scales:
  type:
    body:       { value: 14, from: code, at: 2026-08-10 }
    display-xl: { value: 34, from: figma, at: 2026-07-31 }
  spacing: { value: 4, from: code, at: 2026-08-10 }
  spacingScale: [4, 8, 12, 16, 24, 32, 48, 64]
  radius:  { value: 4, from: code, at: 2026-08-10 }

families:
  allowed: []

forbidden:
  - pattern: raw-hex
  - pattern: off-grid-spacing

Provenance is per-rule, not per-file

Every value carries { from, at }. from is the side that held authority — figma, code, or hand — and at is when.

Why this matters: authority flips. In a real project, sometimes Figma is ahead of the code; sometimes the code is the more current representation and the design frames trail. Without recording which side held authority and when, "which side is stale?" becomes an argument. With provenance, it's a lookup.

When a value's from or value changes, DNA appends a record to .dna/decisions.jsonl:

{"type":"authority-flip","rule":"palette.foreground","scope":"figma→code","reason":"frames trail the reconciled tokens","at":"2026-08-10"}

Tiers: primitive and semantic

If your project uses a two-tier token structure (primitive tokens aliased by semantic tokens), DNA infers the tiers from the CSS alias graph — it does not require any naming convention or config.

palette:
  --color-blue-500:
    value: "#3B82F6"
    tier: primitive
    from: code
    at: 2026-08-10
  --color-brand:
    value: "#3B82F6"
    tier: semantic
    resolves: "--color-blue-500"
    from: code
    at: 2026-08-10

Flat projects (no alias graph) see no tier fields anywhere — neither in the identity nor in any rule output. The tool adapts to the project; it never imposes structure.

shadcn / Tailwind HSL-channel tokens

The shadcn/ui convention stores bare HSL channels in a custom property and composes them at use sites:

:root { --primary: 142 71% 29%; }
.btn  { background: hsl(var(--primary)); }

DNA recognizes these as colors and extracts them into the palette with their resolved hex — but only when real usage corroborates them: the value must have the HSL-channel shape (percent signs on saturation and lightness) and be genuinely composed via hsl(var(--x)) / hsla(var(--x)) somewhere in live source. A bare number triple, or a composition that exists only in a comment, never qualifies. Zero-fabrication is structural, not a tuning knob.

With recognition in place, the full round-trip works on a shadcn codebase: the token extracts, dna resolve names it for a hex input, and bg-[#hex] downgrades to a prefer-token-class finding naming the token.

Adopting exceptions: dna allow

Values enter the identity two ways: extraction (dna start) and deliberate adoption:

dna allow radius 10px --as chip --why "marketing cards"

dna allow writes the value into identity.yaml's enforced section with provenance, and appends the decision — including your --why — to the same .dna/decisions.jsonl ledger that records authority flips. The next dna check accepts it immediately. See Commands → dna allow for survival semantics across dna start --force.

How the loader works

dna check and all MCP tools prefer .dna/identity.yaml when present, then fall back to ds-lint.config.json, then return null (no identity → the seed message, exit 0, zero writes).

The identity is the single source of truth for:

  • Which color tokens are allowed (palette names become the allowlist)
  • What the type scale is (for arbitrary-values checks on font sizes)
  • What the spacing grid is (for off-grid-spacing)
  • Which Tailwind color families are permitted

Next steps

Learn how DNA tracks conformance across source and rendered output in Conformance.