The design-token flow
This literate script is evaluated at documentation-build time (fsdocs build
--eval), so it is compiler-verified against the real FS.Skia.UI.Controls
design-token surface.
Design tokens have a single source of truth: the DTCG file
src/Controls/design-tokens.tokens.json (authored from the Penpot / DTCG design
source). From it, a generated F# module DesignTokens (with Light and Dark
sub-modules) is produced, and the DesignTokenDrift gate keeps the generated F#
in lock-step with the JSON. Themes are built from those typed primitives, and the
control suite reads the theme at render time. This script walks that flow from the
typed token values outward — no design tooling or GPU required.
#I "../../src/Controls/bin/Debug/net10.0"
#r "FS.Skia.UI.Scene.dll"
#r "FS.Skia.UI.Layout.dll"
#r "FS.Skia.UI.Controls.dll"
open FS.Skia.UI.Controls
Typed token primitives
DesignTokens.Light / DesignTokens.Dark are compiler-checked values generated
from the DTCG source. Because they are real F# values, a typo or a removed token
is a compile error, and references to them are greppable.
printfn "light foreground : %A" DesignTokens.Light.foreground
printfn "light background : %A" DesignTokens.Light.background
printfn "light accent : %A" DesignTokens.Light.accent
printfn "dark accent : %A" DesignTokens.Dark.accent
Some dark-theme tokens are aliases of their light counterparts in the DTCG
source (e.g. dark.danger references light.danger); the generator resolves the
alias so both evaluate to the same value:
printfn "light danger : %A" DesignTokens.Light.danger
printfn "dark danger : %A" DesignTokens.Dark.danger
printfn "danger aliased? : %b" (DesignTokens.Light.danger = DesignTokens.Dark.danger)
Non-colour primitives (sizing, density, contrast) flow through the same source:
printfn "font size : %.1f" DesignTokens.Light.fontSize
printfn "density : %.1f" DesignTokens.Light.density
printfn "corner radius : %.1f" DesignTokens.Light.cornerRadius
printfn "min contrast : %.2f" DesignTokens.Light.contrastRequiredRatio
From tokens to a theme
Theme.light / Theme.dark are assembled from these primitives, and the
Theme.with* combinators let an app override individual tokens while keeping the
rest. Here we take the light theme and swap in the dark accent token — exactly how
a consumer applies a brand colour sourced from the design system:
let brandTheme = Theme.light |> Theme.withAccent DesignTokens.Dark.accent
The control suite reads the resolved Theme at render time, so every control
picks up the token values without per-control wiring. To change the palette,
edit the DTCG JSON and regenerate (./fake.sh build -t RefreshSurfaceBaselines);
DesignTokenDrift fails the build if the generated F# and the JSON disagree.
See the design-token / Penpot deep dive, the typed front door, and the API reference.
<summary> Typed, compiler-checked design-token values generated from `src/Controls/design-tokens.tokens.json` (the DTCG single source of truth). Token VALUES are generated; this curated signature is the sole public-surface declaration. Token references are greppable and stay in lock-step with the DTCG source via DesignTokenDrift. </summary>
<summary> Light-theme primitives (feed Theme.light; value-identical to the pre-feature literals). </summary>
<summary> Light-theme primary foreground (text/icon) colour. </summary>
<summary> Light-theme surface/background colour. </summary>
<summary> Light-theme accent colour for primary/active emphasis. </summary>
<summary> Dark-theme primitives (feed Theme.dark; value-identical to the pre-feature literals). </summary>
<summary> Dark-theme accent colour for primary/active emphasis. </summary>
<summary> Light-theme danger/destructive colour for errors and destructive actions. </summary>
<summary> Dark-theme danger/destructive colour (aliases the light-theme danger token in the DTCG source). </summary>
<summary> Light-theme base font size in device-independent units. </summary>
<summary> Light-theme density multiplier scaling spacing and control sizing. </summary>
<summary> Light-theme default corner radius for rounded control surfaces. </summary>
<summary> Minimum foreground/background contrast ratio the light theme must satisfy. </summary>
union case AttrCategory.Theme: AttrCategory
--------------------
module Theme from FS.Skia.UI.Controls
--------------------
type Theme = { Name: string Foreground: Color Background: Color Accent: Color Danger: Color Muted: Color FontFamily: string option FontSize: float Density: float CornerRadius: float ... }
<summary> Design-token palette and metrics (`Theme`): the named color roles (`Foreground`/`Background`/`Accent`/`Danger`/`Muted`), typography (`FontFamily`/`FontSize`), and layout metrics (`Density`/`CornerRadius`/`ContrastRequiredRatio`). </summary>
<summary> The built-in light `Theme` (DTCG `DesignTokens.Light` palette). </summary>
<summary> Return `theme` with its accent colour replaced by `accent`. </summary>