Carpenter Mordant
Typed design tokens and YAML code generation for Flutter.
Mordant turns declarative token files into strongly typed Dart APIs.
Installation
Add Mordant:
dependencies:
carpenter_mordant: ^0.1.0
dev_dependencies:
build_runner: any
Basic usage
Create a Mordant file under lib:
# lib/tokens/tokens.mordant.yaml
palette:
brand:
500: "#987654"
600: "#825E4A"
space:
sm: 0.5rem
md: 1rem
button:
primary:
background: "${palette.brand[500]}"
padding: "${space.md}"
Generate Dart code:
dart run build_runner build --delete-conflicting-outputs
Mordant generates:
lib/tokens/tokens.mordant.g.dart
The generated API can be used directly:
palette.brand[500
]
palette.brand[600
]
space.md
button.primary.background
button.primary.padding
Unit tokens integrate with carpenter_units and resolve through an explicit
runtime context:
final padding = button.primary.padding.resolve(
const LengthUnitContext(rootFontSize: 16),
);
Angle tokens are generated as typed Degrees, Radians, or Turns values:
motion:
loadingAngle: 45deg
final radians = motion.loadingAngle.toRadians().value;
Includes
Large token sets can be split across multiple files.
lib/tokens/
├── tokens.mordant.yaml
├── palette.mordant.part.yaml
├── space.mordant.part.yaml
└── button.mordant.part.yaml
Root file:
$include:
- palette.mordant.part.yaml
- space.mordant.part.yaml
- button.mordant.part.yaml
Includes are resolved relative to the file containing $include.
They may also be nested:
$include:
- components/button.mordant.part.yaml
Included files use the .mordant.part.yaml suffix so they do not generate standalone Dart files.
Duplicate token definitions are rejected.
Circular includes are rejected.
Generated palettes
Mordant can generate a color scale from a seed:
palette:
brand:
$generate: palette
seed:
colorSpace: oklch
components: [0.62, 0.18, 288]
alpha: 1
pivot: 500
gamut: srgb
This produces:
palette.brand[0]
palette.brand[50]
palette.brand[100]
palette.brand[200]
palette.brand[300]
palette.brand[400]
palette.brand[500]
palette.brand[600]
palette.brand[700]
palette.brand[800]
palette.brand[900]
palette.brand[950]
palette.brand[1000]
The default scale contains stops 0, 50 through 950, and 1000. Mordant
uses the OKLCH lightness and chroma curves documented by Carpenter and maps
out-of-gamut colors by reducing chroma while preserving lightness and hue.
Hex seeds and the former start name remain supported for compatibility:
palette:
brand:
$generate: palette
seed: "#987654"
start: 500
Custom steps are supported:
palette:
brand:
$generate: palette
seed: "#987654"
start: 400
steps:
- 50
- 100
- 200
- 300
- 400
- 500
- 600
- 700
- 800
- 900
- 950
Palette lightness targets and chroma envelope can also be configured:
palette:
brand:
$generate: palette
seed:
colorSpace: oklch
components: [0.62, 0.18, 288]
alpha: 1
pivot: 500
gamma: 1.15
lightness:
50: 0.975
950: 0.175
Token values
Mordant currently recognizes:
color: "#987654"
px: 12px
em: 1.25em
rem: 1rem
duration: 200ms
number: 42
decimal: 1.25
enabled: true
label: Hello
reference: "${space.md}"
Hex colors must be quoted because an unquoted # starts a YAML comment.
References
References use ${...} syntax:
space:
md: 1rem
button:
padding: "${space.md}"
Indexed groups may be referenced with bracket syntax:
palette:
brand:
500: "#987654"
button:
background: "${palette.brand[500]}"
References are validated during generation.
Missing and circular references cause the build to fail.
Generated code
Generated files are automatically formatted.
Mordant generates typed Dart structures instead of runtime string lookups.
For example:
button:
primary:
filled:
background: "${palette.brand[500]}"
becomes an API shaped like:
button.primary.filled.background
No runtime token-path parsing is involved.
License
Apache License 2.0.