carpenter_mordant 0.4.1
carpenter_mordant: ^0.4.1 copied to clipboard
Typed design tokens and YAML code generation for Flutter.
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.4.1
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 should be split by semantic ownership rather than by an
arbitrary line count. Keep one .mordant.yaml entry point for code generation
and move complete token groups into .mordant.part.yaml partials.
lib/tokens/
├── tokens.mordant.yaml
├── palette.mordant.part.yaml
├── typography.mordant.part.yaml
├── metrics.mordant.part.yaml
├── interaction.mordant.part.yaml
├── theme-light.mordant.part.yaml
└── theme-dark.mordant.part.yaml
The root file is a small manifest. Include order should follow the dependency flow from primitives to semantic themes:
$include:
- palette.mordant.part.yaml
- typography.mordant.part.yaml
- metrics.mordant.part.yaml
- interaction.mordant.part.yaml
- theme-light.mordant.part.yaml
- theme-dark.mordant.part.yaml
Each partial is merged at the document root. Preserve the complete token path inside it; do not remove the owning top-level group just because it appears in the file name:
# palette.mordant.part.yaml
palette:
brand:
$generate: palette
seed: "#987654"
pivot: 500
# theme-light.mordant.part.yaml
light:
content:
primary: "${palette.brand[900]}"
References may cross partial-file boundaries. Mordant loads and merges the complete include graph before resolving aliases and running generators.
Use these boundaries as a starting point:
- palettes and other primitives;
- typography;
- dimensions, spacing, shape, and borders;
- focus and motion;
- semantic themes or large component-domain groups.
Keep a group together when splitting it would obscure ownership. A component with only a handful of tokens does not need its own file.
Includes are resolved relative to the file containing $include, so related
partials may be grouped in subdirectories. They may also be nested:
$include:
- components/button.mordant.part.yaml
Use the .mordant.part.yaml or .mordant.part.yml suffix for included files.
Only the root .mordant.yaml or .mordant.yml file is a code-generation input,
so partials do not produce standalone Dart libraries.
Includes compose definitions; they do not override them. Duplicate token paths are rejected regardless of include order, and circular includes are rejected.
After adding, removing, or moving a partial, regenerate the root file normally:
dart run build_runner build --delete-conflicting-outputs
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.