Design tokens
Every visual value in yemek is a CSS custom property (--yemek-*), defined in src/tokens/index.css and imported once via yemek/style.css. There is deliberately no JS theme-object API — see Architecture and the reasoning in the repo's CLAUDE.md ("Customizability") for why that's deferred until a second real consumer needs it.
Light is the default :root; dark applies via :root[data-theme='dark'] andprefers-color-scheme: dark (same dual-mechanism pattern meal-tracker itself uses), so a consumer can either respect the OS setting or drive an explicit toggle.
Color
| Token | Light | Dark | Used for |
|---|---|---|---|
--yemek-color-accent | #2f6b52 | #5fae8a | Brand accent — dark moss/jade, not a bright lime or Tailwind's emerald-500 default |
--yemek-color-accent-strong | #204a39 | #7fc4a3 | Accent, emphasized variant |
--yemek-color-highlight | #e08a4b | #eb9d63 | Warm contrast — celebrations, streaks, CTA emphasis |
--yemek-color-highlight-strong | #c96f34 | #f0b283 | Highlight, emphasized variant |
--yemek-color-surface | #fbfaf7 | #17181a | Card/component background — warm off-white / warm anthracite, never pure white/black |
--yemek-color-surface-inset | #f0ede6 | #232427 | Recessed surfaces (e.g. track backgrounds) |
--yemek-color-text | #201f1c | #f2f0ea | Primary text |
--yemek-color-text-muted | #6f6a61 | #9a968c | Secondary/caption text |
--yemek-color-border | #e3ded3 | #2e2f32 | Borders/dividers |
Zone colors
Deliberately not identical to the accent color — otherwise "on track" would just mean "the brand," which carries no real signal.
| Token | Meaning | Light | Dark |
|---|---|---|---|
--yemek-color-zone-under | Still have room (cool, neutral) | #4c7fa6 | #6ea3c9 |
--yemek-color-zone-onTrack | On track — the one exception, equals the accent on purpose | #2f6b52 | #5fae8a |
--yemek-color-zone-over | Over goal (warm, not alarm-red) | #c4483b | #d97767 |
Nutrient colors
One token per nutrient, decided ahead of any multi-nutrient component so future components (e.g. a macro-split bar) consistently pull from the same set instead of picking ad-hoc colors.
| Token | Light | Dark |
|---|---|---|
--yemek-nutrient-protein | #7d6a99 | #a693c4 |
--yemek-nutrient-carbs | #cf9d4f | #e0b768 |
--yemek-nutrient-fat | #c17b6b | #d99c8e |
--yemek-nutrient-calories | = --yemek-color-accent | = --yemek-color-accent |
--yemek-nutrient-water | #3f9c96 | #5fc0ba |
Heat/streak tokens
A single warm hue family end to end (gold → rust → ember) instead of a generic scale that jumps to an unrelated blue at the top tier. StreakHeatmap's middle tiers reuse --yemek-color-highlight/-highlight-strong rather than adding two more parallel colors.
| Token | Light | Dark |
|---|---|---|
--yemek-heat-1 | #d4a24e | #e6bd72 |
--yemek-heat-2 | #a8452f | #c15a3c |
--yemek-heat-3 | #7a2e22 | #9c4530 |
Spacing & radius
| Token | Value |
|---|---|
--yemek-space-1 … --yemek-space-8 | 4px, 8px, 12px, 16px, 24px, 32px |
--yemek-radius-sm | 4px |
--yemek-radius-md | 10px |
--yemek-radius-full | 999px — pills/badges only, deliberately not used for cards/buttons |
Typography
| Token | Value |
|---|---|
--yemek-font-display | 'Familjen Grotesk', ui-sans-serif, system-ui, sans-serif — bold geometric sans for numbers |
--yemek-font-family | 'Work Sans', system-ui, sans-serif — UI text, deliberately not Inter |
--yemek-font-size-sm / -md / -lg / -display | 0.8125rem / 1rem / 1.5rem / 2.5rem |
--yemek-font-weight-medium / -bold / -display | 500 / 700 / 700 |
--yemek-font-feature-numeric | 'tnum' 1 — tabular numbers so a changing value never shifts sideways |
Fonts
Font files are not bundled into the library build — @fontsource's CSS base64-inlines every language subset of every weight (~640KB) regardless of which ones a consumer actually renders. Install @fontsource/familjen-grotesk and @fontsource/work-sans yourself and import only the weights you use.
Motion
Every component animates state changes — no silent, instant value jumps (CLAUDE.md §1a, a hard rule). Signature motion is an elastic ease-out on fill plus a soft glow pulse on goal reached, deliberately standalone from meal-tracker's own "Nitro" shimmer effect.
| Token | Value |
|---|---|
--yemek-motion-fast | 150ms |
--yemek-motion-medium | 350ms |
--yemek-motion-slow | 700ms |
--yemek-motion-ease | cubic-bezier(0.34, 1.56, 0.64, 1) — slight overshoot ("elastic") |
--yemek-motion-ease-linear | cubic-bezier(0.4, 0, 0.2, 1) |
Every animation also has a prefers-reduced-motion: reduce fallback (overshoot/glow collapse to a simple crossfade, or no transition at all) — "always animate" doesn't mean ignoring motion preferences.
Overriding tokens in a host app
Override any subset at your app's root. This is safe for structural/neutral tokens (surface/border/text) even against yemek's own brand colors, since each component reads through the same custom properties:
:root {
--yemek-color-surface: #161c27;
--yemek-color-border: #2a3242;
--yemek-color-text: #f4f6fb;
}One caution learned from meal-tracker's own integration: a token tuned only against yemek's own --yemek-color-surface isn't guaranteed to contrast against an arbitrary host background (an opaque hex can coincidentally match). Where contrast matters regardless of host surface (e.g. MetricRing's track), the component derives its color from --yemek-color-text via color-mix() instead of a fixed surface-relative token.