Theme Generator

Eluan ships one built-in theme on purpose — your brand's look is a theme you generate. Give the generator an accent color and it derives the full ramp: primitives, semantic tokens, system hues, and a tinted neutral scale, contrast-checked and auto-corrected across light and dark.

Playground

Everything below runs in your browser — @eluan/theme-generator's programmatic entry point is pure color math with no Node built-ins, and a full generation takes a couple of milliseconds, so the preview regenerates on every keystroke.

The preview only owns the theme layer. Mode, spacing, and curves still come from the switchers in the header, so you are looking at all four axes composed — try flipping to dark while a generated theme is applied.

Accentup to 3
Theme nameused in the CSS selector
Neutral tint0.04 — accent bleed into the greys
Contrast4.5:1 floor — WCAG AA
Buttons
Input
Card
Notifications
On
Delivery preferences
Push alerts
Banners

Contrast report

Every foreground and border token that declares a background to sit on is checked in both modes. generateTheme runs autoCorrect first, walking a failing token up or down its own ramp (and falling back to plain white or black) until it clears the target — so the summary below is the state after correction.

Each pair's target is the minContrast floor — 4.5:1 on AA, 7:1 on AAA via the toggle above. Tokens in the semantic map can pin a higher minimum of their own, and exactly one opts out downward: interactive-fg-alt, secondary text inside interactive components, stays at the 3:1 large-text tier via allowBelowDefault even on AAA.

One thing AAA cannot do is manufacture contrast an accent doesn't have. When no step on a ramp clears the floor, autoCorrect saturates at pure white or black and records the shortfall in result.corrections — so for strongly saturated accents the AAA output can come out nearly identical to AA, because the failing pairs were already at the strongest foreground the accent allows.

62/62 pairs pass4.5:1 floor, 3:1 for secondary text · light + dark12 auto-corrected
All 62 pairs
  • container-fg on container-bg17.32 / 4.5pass
  • container-fg-alt on container-bg10.37 / 4.5pass
  • container-fg-inverse on container-bg-inverse10.37 / 4.5pass
  • interactive-fg on interactive-bg17.32 / 4.5pass
  • interactive-fg-alt on interactive-bg5.51 / 4.5pass
  • interactive-fg-readonly on interactive-bg10.37 / 4.5pass
  • interactive-fg-active on interactive-bg-active9.56 / 4.5pass
  • interactive-fg-selected on interactive-bg-selected9.56 / 4.5pass
  • interactive-fg-inverse on interactive-bg-inverse17.32 / 4.5pass
  • action-primary-fg on action-primary-bgcorrected4.67 / 4.5pass
  • action-primary-fg-active on action-primary-bg-active10.92 / 4.5pass
  • action-primary-fg-selected on action-primary-bg-selected10.92 / 4.5pass
  • action-primary-fg-inverse on action-primary-bg-inverse8.21 / 4.5pass
  • action-secondary-fg on container-bgcorrected17.46 / 4.5pass
  • action-secondary-fg-active on action-secondary-bg-activecorrected4.67 / 4.5pass
  • action-secondary-fg-selected on action-secondary-bg-selectedcorrected4.67 / 4.5pass
  • action-tertiary-fg on action-tertiary-bgcorrected17.46 / 4.5pass
  • action-tertiary-fg-active on action-tertiary-bg-active7.23 / 4.5pass
  • action-tertiary-fg-selected on action-tertiary-bg-selected7.23 / 4.5pass
  • destructive-fg on destructive-bg8.39 / 4.5pass
  • destructive-fg-active on destructive-bg-active11.03 / 4.5pass
  • destructive-fg-selected on destructive-bg-selected11.03 / 4.5pass
  • destructive-fg-inverse on destructive-bg-inverse10.98 / 4.5pass
  • positive-fg on positive-bg7.40 / 4.5pass
  • positive-fg-inverse on positive-bg-inverse10.00 / 4.5pass
  • cautionary-fg on cautionary-bg7.86 / 4.5pass
  • cautionary-fg-inverse on cautionary-bg-inverse10.47 / 4.5pass
  • informative-fg on informative-bg7.89 / 4.5pass
  • informative-fg-inverse on informative-bg-inverse10.50 / 4.5pass
  • important-fg on important-bg8.21 / 4.5pass
  • important-fg-inverse on important-bg-inverse10.86 / 4.5pass
  • container-fg on container-bg10.37 / 4.5pass
  • container-fg-alt on container-bg9.16 / 4.5pass
  • container-fg-inverse on container-bg-inverse15.30 / 4.5pass
  • interactive-fg on interactive-bg10.37 / 4.5pass
  • interactive-fg-alt on interactive-bg5.63 / 4.5pass
  • interactive-fg-readonly on interactive-bg9.16 / 4.5pass
  • interactive-fg-active on interactive-bg-active7.39 / 4.5pass
  • interactive-fg-selected on interactive-bg-selected7.39 / 4.5pass
  • interactive-fg-inverse on interactive-bg-inverse17.32 / 4.5pass
  • action-primary-fg on action-primary-bgcorrected4.67 / 4.5pass
  • action-primary-fg-active on action-primary-bg-activecorrected6.72 / 4.5pass
  • action-primary-fg-selected on action-primary-bg-selectedcorrected6.72 / 4.5pass
  • action-primary-fg-inverse on action-primary-bg-inverse12.57 / 4.5pass
  • action-secondary-fg on container-bgcorrected10.31 / 4.5pass
  • action-secondary-fg-active on action-secondary-bg-activecorrected4.67 / 4.5pass
  • action-secondary-fg-selected on action-secondary-bg-selectedcorrected4.67 / 4.5pass
  • action-tertiary-fg on action-tertiary-bgcorrected10.31 / 4.5pass
  • action-tertiary-fg-active on action-tertiary-bg-active10.25 / 4.5pass
  • action-tertiary-fg-selected on action-tertiary-bg-selected10.25 / 4.5pass
  • destructive-fg on destructive-bg12.48 / 4.5pass
  • destructive-fg-active on destructive-bg-active8.99 / 4.5pass
  • destructive-fg-selected on destructive-bg-selected8.99 / 4.5pass
  • destructive-fg-inverse on destructive-bg-inverse12.48 / 4.5pass
  • positive-fg on positive-bg12.88 / 4.5pass
  • positive-fg-inverse on positive-bg-inverse12.88 / 4.5pass
  • cautionary-fg on cautionary-bg12.61 / 4.5pass
  • cautionary-fg-inverse on cautionary-bg-inverse12.61 / 4.5pass
  • informative-fg on informative-bg12.77 / 4.5pass
  • informative-fg-inverse on informative-bg-inverse12.77 / 4.5pass
  • important-fg on important-bg12.57 / 4.5pass
  • important-fg-inverse on important-bg-inverse12.57 / 4.5pass
12 tokens auto-corrected
  • action-primary-fglightneutral.50white4.29 4.67 (hard-fallback)
  • action-secondary-fglightprimary.500primary.9504.29 17.46 (step-shift)
  • action-secondary-fg-activelightneutral.50white4.29 4.67 (hard-fallback)
  • action-secondary-fg-selectedlightneutral.50white4.29 4.67 (hard-fallback)
  • action-tertiary-fglightprimary.500primary.9504.29 17.46 (step-shift)
  • action-primary-fgdarkneutral.50white4.29 4.67 (hard-fallback)
  • action-primary-fg-activedarkneutral.50black2.87 6.72 (hard-fallback)
  • action-primary-fg-selecteddarkneutral.50black2.87 6.72 (hard-fallback)
  • action-secondary-fgdarkprimary.400primary.503.61 10.31 (step-shift)
  • action-secondary-fg-activedarkneutral.50white4.29 4.67 (hard-fallback)
  • action-secondary-fg-selecteddarkneutral.50white4.29 4.67 (hard-fallback)
  • action-tertiary-fgdarkprimary.400primary.503.61 10.31 (step-shift)

Export

custom.css

result.css.theme — both modes, every value already resolved to a literal hex. Import it anywhere after @eluan/core/styles.css and set data-theme="custom".

/* Generated by eluan-theme-generator v0.1.0 */
/* Input hash: 0621956955be */
/* Theme: custom */

[data-theme="custom"][data-mode="light"],
[data-theme="custom"][data-mode="Light"] {
  --container-bg: #f6f5f5; /* neutral.50 */
  --container-bg-alt: #e9e7e7; /* neutral.100 */
  --container-bg-inverse: #3d3a38; /* neutral.800 */
  --container-border: #d6d3d3; /* neutral.200 */
  --container-border-alt: #bbb6b5; /* neutral.300 */
  --container-border-inverse: #686260; /* neutral.600 */
  --container-fg: #121111; /* neutral.950 */
  --container-fg-alt: #3d3a38; /* neutral.800 */
  --container-fg-inverse: #f6f5f5; /* neutral.50 */
  --interactive-bg: #f6f5f5; /* neutral.50 */
  --interactive-bg-alt: #e9e7e7; /* neutral.100 */
  --interactive-bg-alt2: #fff2ee; /* primary.50 */
  --interactive-bg-disabled: #d6d3d3; /* neutral.200 */
  --interactive-bg-hover: #d6d3d3; /* neutral.200 */
  --interactive-bg-active: #691c00; /* primary.800 */
  --interactive-bg-selected: #691c00; /* primary.800 */
  --interactive-bg-inverse: #121111; /* neutral.950 */
  --interactive-border: #d6d3d3; /* neutral.200 */
  --interactive-border-alt: #bbb6b5; /* neutral.300 */
  --interactive-border-disabled: #bbb6b5; /* neutral.300 */
  --interactive-fg: #121111; /* neutral.950 */
  --interactive-fg-alt: #686260; /* neutral.600 */
  --interactive-fg-readonly: #3d3a38; /* neutral.800 */
  --interactive-fg-disabled: #9d9795; /* neutral.400 */
  --interactive-fg-active: #ffe0d7; /* primary.100 */
  --interactive-fg-selected: #ffe0d7; /* primary.100 */
  --interactive-fg-inverse: #f6f5f5; /* neutral.50 */
  --action-primary-bg: #d24100; /* primary.500 */
  --action-primary-bg-hover: #691c00; /* primary.800 */
  --action-primary-bg-active: #691c00; /* primary.800 */
  --action-primary-bg-selected: #691c00; /* primary.800 */
  --action-primary-bg-disabled: #ffe0d7; /* primary.100 */
  --action-primary-bg-inverse: #fff2ee; /* primary.50 */
  --action-primary-fg: #ffffff; /* white */
  --action-primary-fg-disabled: #9d9795; /* neutral.400 */
  --action-primary-fg-active: #f6f5f5; /* neutral.50 */
  --action-primary-fg-selected: #f6f5f5; /* neutral.50 */
  --action-primary-fg-inverse: #882700; /* primary.700 */
  --action-secondary-bg-hover: #ffe0d7; /* primary.100 */
  --action-secondary-bg-active: #d24100; /* primary.500 */
  --action-secondary-bg-selected: #d24100; /* primary.500 */
  --action-secondary-bg-disabled: #d6d3d3; /* neutral.200 */
  --action-secondary-border: #d24100; /* primary.500 */
  --action-secondary-border-alt: #691c00; /* primary.800 */
  --action-secondary-border-disabled: #ffe0d7; /* primary.100 */
  --action-secondary-border-inverse: #fff2ee; /* primary.50 */
  --action-secondary-fg: #250601; /* primary.950 */
  --action-secondary-fg-alt: #121111; /* neutral.950 */
  --action-secondary-fg-disabled: #9d9795; /* neutral.400 */
  --action-secondary-fg-active: #ffffff; /* white */
  --action-secondary-fg-selected: #ffffff; /* white */
  --action-secondary-fg-inverse: #121111; /* neutral.950 */
  --action-tertiary-bg: #f6f5f5; /* neutral.50 */
  --action-tertiary-bg-alt: #e9e7e7; /* neutral.100 */
  --action-tertiary-bg-hover: #fff2ee; /* primary.50 */
  --action-tertiary-bg-active: #ffe0d7; /* primary.100 */
  --action-tertiary-bg-selected: #ffe0d7; /* primary.100 */
  --action-tertiary-bg-disabled: #f6f5f5; /* neutral.50 */
  --action-tertiary-bg-inverse: #f6f5f5; /* neutral.50 */
  --action-tertiary-border: #d24100; /* primary.500 */
  --action-tertiary-border-alt: #691c00; /* primary.800 */
  --action-tertiary-border-disabled: #bbb6b5; /* neutral.300 */
  --action-tertiary-border-inverse: #ffe0d7; /* primary.100 */
  --action-tertiary-fg: #250601; /* primary.950 */
  --action-tertiary-fg-alt: #121111; /* neutral.950 */
  --action-tertiary-fg-disabled: #9d9795; /* neutral.400 */
  --action-tertiary-fg-active: #882700; /* primary.700 */
  --action-tertiary-fg-selected: #882700; /* primary.700 */
  --action-tertiary-fg-inverse: #f6f5f5; /* neutral.50 */
  --destructive-bg: #fff2f0; /* error.50 */
  --destructive-bg-alt: #ffe0db; /* error.100 */
  --destructive-bg-hover: #ffe0db; /* error.100 */
  --destructive-bg-active: #6b1614; /* error.800 */
  --destructive-bg-selected: #6b1614; /* error.800 */
  --destructive-bg-disabled: #d6d3d3; /* neutral.200 */
  --destructive-bg-inverse: #6b1614; /* error.800 */
  --destructive-border: #d73532; /* error.500 */
  --destructive-border-alt: #6b1614; /* error.800 */
  --destructive-border-disabled: #ffe0db; /* error.100 */
  --destructive-border-inverse: #fff2f0; /* error.50 */
  --destructive-fg: #901315; /* error.700 */
  --destructive-fg-alt: #121111; /* neutral.950 */
  --destructive-fg-disabled: #9d9795; /* neutral.400 */
  --destructive-fg-active: #f6f5f5; /* neutral.50 */
  --destructive-fg-selected: #f6f5f5; /* neutral.50 */
  --destructive-fg-inverse: #fff2f0; /* error.50 */
  --positive-bg: #edf9ee; /* success.50 */
  --positive-bg-alt: #d9efda; /* success.100 */
  --positive-bg-inverse: #004815; /* success.800 */
  --positive-border: #009434; /* success.500 */
  --positive-border-alt: #004815; /* success.800 */
  --positive-border-inverse: #edf9ee; /* success.50 */
  --positive-fg: #005e1e; /* success.700 */
  --positive-fg-alt: #121111; /* neutral.950 */
  --positive-fg-inverse: #edf9ee; /* success.50 */
  --cautionary-bg: #fdf4e7; /* warning.50 */
  --cautionary-bg-alt: #f6e5ce; /* warning.100 */
  --cautionary-bg-inverse: #513400; /* warning.800 */
  --cautionary-border: #a46e00; /* warning.500 */
  --cautionary-border-alt: #513400; /* warning.800 */
  --cautionary-border-inverse: #fdf4e7; /* warning.50 */
  --cautionary-fg: #694500; /* warning.700 */
  --cautionary-fg-alt: #121111; /* neutral.950 */
  --cautionary-fg-inverse: #fdf4e7; /* warning.50 */
  --informative-bg: #eff6ff; /* info.50 */
  --informative-bg-alt: #d9eaff; /* info.100 */
  --informative-bg-inverse: #003974; /* info.800 */
  --informative-border: #0078e7; /* info.500 */
  --informative-border-alt: #003974; /* info.800 */
  --informative-border-inverse: #eff6ff; /* info.50 */
  --informative-fg: #004b96; /* info.700 */
  --informative-fg-alt: #121111; /* neutral.950 */
  --informative-fg-inverse: #eff6ff; /* info.50 */
  --important-bg: #fff2ee; /* primary.50 */
  --important-bg-alt: #ffe0d7; /* primary.100 */
  --important-bg-inverse: #691c00; /* primary.800 */
  --important-border: #d24100; /* primary.500 */
  --important-border-alt: #691c00; /* primary.800 */
  --important-border-inverse: #fff2ee; /* primary.50 */
  --important-fg: #882700; /* primary.700 */
  --important-fg-alt: #121111; /* neutral.950 */
  --important-fg-inverse: #fff2ee; /* primary.50 */
  --dataviz-1-main: #d24100; /* primary.500 */
  --dataviz-1-tint: #ffe0d7; /* primary.100 */
  --dataviz-1-shade: #691c00; /* primary.800 */
  --dataviz-2-main: #0078e7; /* info.500 */
  --dataviz-2-tint: #d9eaff; /* info.100 */
  --dataviz-2-shade: #003974; /* info.800 */
  --dataviz-3-main: #009434; /* success.500 */
  --dataviz-3-tint: #d9efda; /* success.100 */
  --dataviz-3-shade: #004815; /* success.800 */
  --dataviz-4-main: #ac3400; /* primary.600 */
  --dataviz-4-tint: #ffc5b3; /* primary.200 */
  --dataviz-4-shade: #481100; /* primary.900 */
  --dataviz-5-main: #a46e00; /* warning.500 */
  --dataviz-5-tint: #f6e5ce; /* warning.100 */
  --dataviz-5-shade: #513400; /* warning.800 */
  --dataviz-6-main: #d73532; /* error.500 */
  --dataviz-6-tint: #ffe0db; /* error.100 */
  --dataviz-6-shade: #6b1614; /* error.800 */
  --dataviz-7-main: #007829; /* success.600 */
  --dataviz-7-tint: #b7e1ba; /* success.200 */
  --dataviz-7-shade: #01300c; /* success.900 */
  --dataviz-8-main: #0061be; /* info.600 */
  --dataviz-8-tint: #b8d7ff; /* info.200 */
  --dataviz-8-shade: #05264c; /* info.900 */
}

[data-theme="custom"][data-mode="dark"],
[data-theme="custom"][data-mode="Dark"] {
  --container-bg: #3d3a38; /* neutral.800 */
  --container-bg-alt: #282625; /* neutral.900 */
  --container-bg-inverse: #e9e7e7; /* neutral.100 */
  --container-border: #514c4a; /* neutral.700 */
  --container-border-alt: #686260; /* neutral.600 */
  --container-border-inverse: #d6d3d3; /* neutral.200 */
  --container-fg: #f6f5f5; /* neutral.50 */
  --container-fg-alt: #e9e7e7; /* neutral.100 */
  --container-fg-inverse: #121111; /* neutral.950 */
  --interactive-bg: #3d3a38; /* neutral.800 */
  --interactive-bg-alt: #282625; /* neutral.900 */
  --interactive-bg-alt2: #250601; /* primary.950 */
  --interactive-bg-disabled: #514c4a; /* neutral.700 */
  --interactive-bg-hover: #514c4a; /* neutral.700 */
  --interactive-bg-active: #ff9878; /* primary.300 */
  --interactive-bg-selected: #ff9878; /* primary.300 */
  --interactive-bg-inverse: #f6f5f5; /* neutral.50 */
  --interactive-border: #514c4a; /* neutral.700 */
  --interactive-border-alt: #686260; /* neutral.600 */
  --interactive-border-disabled: #686260; /* neutral.600 */
  --interactive-fg: #f6f5f5; /* neutral.50 */
  --interactive-fg-alt: #bbb6b5; /* neutral.300 */
  --interactive-fg-readonly: #e9e7e7; /* neutral.100 */
  --interactive-fg-disabled: #7f7977; /* neutral.500 */
  --interactive-fg-active: #481100; /* primary.900 */
  --interactive-fg-selected: #481100; /* primary.900 */
  --interactive-fg-inverse: #121111; /* neutral.950 */
  --action-primary-bg: #d24100; /* primary.500 */
  --action-primary-bg-hover: #f46435; /* primary.400 */
  --action-primary-bg-active: #f46435; /* primary.400 */
  --action-primary-bg-selected: #f46435; /* primary.400 */
  --action-primary-bg-disabled: #481100; /* primary.900 */
  --action-primary-bg-inverse: #250601; /* primary.950 */
  --action-primary-fg: #ffffff; /* white */
  --action-primary-fg-disabled: #686260; /* neutral.600 */
  --action-primary-fg-active: #000000; /* black */
  --action-primary-fg-selected: #000000; /* black */
  --action-primary-fg-inverse: #ffc5b3; /* primary.200 */
  --action-secondary-bg-hover: #481100; /* primary.900 */
  --action-secondary-bg-active: #d24100; /* primary.500 */
  --action-secondary-bg-selected: #d24100; /* primary.500 */
  --action-secondary-bg-disabled: #514c4a; /* neutral.700 */
  --action-secondary-border: #f46435; /* primary.400 */
  --action-secondary-border-alt: #ff9878; /* primary.300 */
  --action-secondary-border-disabled: #481100; /* primary.900 */
  --action-secondary-border-inverse: #250601; /* primary.950 */
  --action-secondary-fg: #fff2ee; /* primary.50 */
  --action-secondary-fg-alt: #f6f5f5; /* neutral.50 */
  --action-secondary-fg-disabled: #7f7977; /* neutral.500 */
  --action-secondary-fg-active: #ffffff; /* white */
  --action-secondary-fg-selected: #ffffff; /* white */
  --action-secondary-fg-inverse: #f6f5f5; /* neutral.50 */
  --action-tertiary-bg: #3d3a38; /* neutral.800 */
  --action-tertiary-bg-alt: #282625; /* neutral.900 */
  --action-tertiary-bg-hover: #250601; /* primary.950 */
  --action-tertiary-bg-active: #481100; /* primary.900 */
  --action-tertiary-bg-selected: #481100; /* primary.900 */
  --action-tertiary-bg-disabled: #3d3a38; /* neutral.800 */
  --action-tertiary-bg-inverse: #3d3a38; /* neutral.800 */
  --action-tertiary-border: #f46435; /* primary.400 */
  --action-tertiary-border-alt: #ff9878; /* primary.300 */
  --action-tertiary-border-disabled: #686260; /* neutral.600 */
  --action-tertiary-border-inverse: #481100; /* primary.900 */
  --action-tertiary-fg: #fff2ee; /* primary.50 */
  --action-tertiary-fg-alt: #f6f5f5; /* neutral.50 */
  --action-tertiary-fg-disabled: #7f7977; /* neutral.500 */
  --action-tertiary-fg-active: #ffc5b3; /* primary.200 */
  --action-tertiary-fg-selected: #ffc5b3; /* primary.200 */
  --action-tertiary-fg-inverse: #121111; /* neutral.950 */
  --destructive-bg: #230706; /* error.950 */
  --destructive-bg-alt: #46120f; /* error.900 */
  --destructive-bg-hover: #46120f; /* error.900 */
  --destructive-bg-active: #fa998e; /* error.300 */
  --destructive-bg-selected: #fa998e; /* error.300 */
  --destructive-bg-disabled: #514c4a; /* neutral.700 */
  --destructive-bg-inverse: #ffc3bc; /* error.200 */
  --destructive-border: #ee675d; /* error.400 */
  --destructive-border-alt: #fa998e; /* error.300 */
  --destructive-border-disabled: #46120f; /* error.900 */
  --destructive-border-inverse: #230706; /* error.950 */
  --destructive-fg: #ffc3bc; /* error.200 */
  --destructive-fg-alt: #f6f5f5; /* neutral.50 */
  --destructive-fg-disabled: #7f7977; /* neutral.500 */
  --destructive-fg-active: #121111; /* neutral.950 */
  --destructive-fg-selected: #121111; /* neutral.950 */
  --destructive-fg-inverse: #230706; /* error.950 */
  --positive-bg: #011704; /* success.950 */
  --positive-bg-alt: #01300c; /* success.900 */
  --positive-bg-inverse: #b7e1ba; /* success.200 */
  --positive-border: #3fb355; /* success.400 */
  --positive-border-alt: #82cc8a; /* success.300 */
  --positive-border-inverse: #011704; /* success.950 */
  --positive-fg: #b7e1ba; /* success.200 */
  --positive-fg-alt: #f6f5f5; /* neutral.50 */
  --positive-fg-inverse: #011704; /* success.950 */
  --cautionary-bg: #1b0f00; /* warning.950 */
  --cautionary-bg-alt: #362200; /* warning.900 */
  --cautionary-bg-inverse: #f0cea2; /* warning.200 */
  --cautionary-border: #cb8900; /* warning.400 */
  --cautionary-border-alt: #e4ac5a; /* warning.300 */
  --cautionary-border-inverse: #1b0f00; /* warning.950 */
  --cautionary-fg: #f0cea2; /* warning.200 */
  --cautionary-fg-alt: #f6f5f5; /* neutral.50 */
  --cautionary-fg-inverse: #1b0f00; /* warning.950 */
  --informative-bg: #021127; /* info.950 */
  --informative-bg-alt: #05264c; /* info.900 */
  --informative-bg-inverse: #b8d7ff; /* info.200 */
  --informative-border: #4799fd; /* info.400 */
  --informative-border-alt: #85baff; /* info.300 */
  --informative-border-inverse: #021127; /* info.950 */
  --informative-fg: #b8d7ff; /* info.200 */
  --informative-fg-alt: #f6f5f5; /* neutral.50 */
  --informative-fg-inverse: #021127; /* info.950 */
  --important-bg: #250601; /* primary.950 */
  --important-bg-alt: #481100; /* primary.900 */
  --important-bg-inverse: #ffc5b3; /* primary.200 */
  --important-border: #f46435; /* primary.400 */
  --important-border-alt: #ff9878; /* primary.300 */
  --important-border-inverse: #250601; /* primary.950 */
  --important-fg: #ffc5b3; /* primary.200 */
  --important-fg-alt: #f6f5f5; /* neutral.50 */
  --important-fg-inverse: #250601; /* primary.950 */
  --dataviz-1-main: #f46435; /* primary.400 */
  --dataviz-1-tint: #481100; /* primary.900 */
  --dataviz-1-shade: #ffc5b3; /* primary.200 */
  --dataviz-2-main: #4799fd; /* info.400 */
  --dataviz-2-tint: #05264c; /* info.900 */
  --dataviz-2-shade: #b8d7ff; /* info.200 */
  --dataviz-3-main: #3fb355; /* success.400 */
  --dataviz-3-tint: #01300c; /* success.900 */
  --dataviz-3-shade: #b7e1ba; /* success.200 */
  --dataviz-4-main: #ff9878; /* primary.300 */
  --dataviz-4-tint: #691c00; /* primary.800 */
  --dataviz-4-shade: #ffe0d7; /* primary.100 */
  --dataviz-5-main: #cb8900; /* warning.400 */
  --dataviz-5-tint: #362200; /* warning.900 */
  --dataviz-5-shade: #f0cea2; /* warning.200 */
  --dataviz-6-main: #ee675d; /* error.400 */
  --dataviz-6-tint: #46120f; /* error.900 */
  --dataviz-6-shade: #ffc3bc; /* error.200 */
  --dataviz-7-main: #82cc8a; /* success.300 */
  --dataviz-7-tint: #004815; /* success.800 */
  --dataviz-7-shade: #d9efda; /* success.100 */
  --dataviz-8-main: #85baff; /* info.300 */
  --dataviz-8-tint: #003974; /* info.800 */
  --dataviz-8-shade: #d9eaff; /* info.100 */
}
createTheme()

result.createThemeTokens wrapped in the createTheme call shape. createTheme emits one mode-agnostic rule, so this is the light-mode set — use the CSS above if you need dark mode too.

import { createTheme } from "@eluan/core";

export const custom = createTheme({
  name: "custom",
  // extends: "minimal" — the only built-in, and the default
  tokens: {
    "--container-bg": "#f6f5f5",
    "--container-bg-alt": "#e9e7e7",
    "--container-bg-inverse": "#3d3a38",
    "--container-border": "#d6d3d3",
    "--container-border-alt": "#bbb6b5",
    "--container-border-inverse": "#686260",
    "--container-fg": "#121111",
    "--container-fg-alt": "#3d3a38",
    "--container-fg-inverse": "#f6f5f5",
    "--interactive-bg": "#f6f5f5",
    "--interactive-bg-alt": "#e9e7e7",
    "--interactive-bg-alt2": "#fff2ee",
    "--interactive-bg-disabled": "#d6d3d3",
    "--interactive-bg-hover": "#d6d3d3",
    "--interactive-bg-active": "#691c00",
    "--interactive-bg-selected": "#691c00",
    "--interactive-bg-inverse": "#121111",
    "--interactive-border": "#d6d3d3",
    "--interactive-border-alt": "#bbb6b5",
    "--interactive-border-disabled": "#bbb6b5",
    "--interactive-fg": "#121111",
    "--interactive-fg-alt": "#686260",
    "--interactive-fg-readonly": "#3d3a38",
    "--interactive-fg-disabled": "#9d9795",
    "--interactive-fg-active": "#ffe0d7",
    "--interactive-fg-selected": "#ffe0d7",
    "--interactive-fg-inverse": "#f6f5f5",
    "--action-primary-bg": "#d24100",
    "--action-primary-bg-hover": "#691c00",
    "--action-primary-bg-active": "#691c00",
    "--action-primary-bg-selected": "#691c00",
    "--action-primary-bg-disabled": "#ffe0d7",
    "--action-primary-bg-inverse": "#fff2ee",
    "--action-primary-fg": "#ffffff",
    "--action-primary-fg-disabled": "#9d9795",
    "--action-primary-fg-active": "#f6f5f5",
    "--action-primary-fg-selected": "#f6f5f5",
    "--action-primary-fg-inverse": "#882700",
    "--action-secondary-bg-hover": "#ffe0d7",
    "--action-secondary-bg-active": "#d24100",
    "--action-secondary-bg-selected": "#d24100",
    "--action-secondary-bg-disabled": "#d6d3d3",
    "--action-secondary-border": "#d24100",
    "--action-secondary-border-alt": "#691c00",
    "--action-secondary-border-disabled": "#ffe0d7",
    "--action-secondary-border-inverse": "#fff2ee",
    "--action-secondary-fg": "#250601",
    "--action-secondary-fg-alt": "#121111",
    "--action-secondary-fg-disabled": "#9d9795",
    "--action-secondary-fg-active": "#ffffff",
    "--action-secondary-fg-selected": "#ffffff",
    "--action-secondary-fg-inverse": "#121111",
    "--action-tertiary-bg": "#f6f5f5",
    "--action-tertiary-bg-alt": "#e9e7e7",
    "--action-tertiary-bg-hover": "#fff2ee",
    "--action-tertiary-bg-active": "#ffe0d7",
    "--action-tertiary-bg-selected": "#ffe0d7",
    "--action-tertiary-bg-disabled": "#f6f5f5",
    "--action-tertiary-bg-inverse": "#f6f5f5",
    "--action-tertiary-border": "#d24100",
    "--action-tertiary-border-alt": "#691c00",
    "--action-tertiary-border-disabled": "#bbb6b5",
    "--action-tertiary-border-inverse": "#ffe0d7",
    "--action-tertiary-fg": "#250601",
    "--action-tertiary-fg-alt": "#121111",
    "--action-tertiary-fg-disabled": "#9d9795",
    "--action-tertiary-fg-active": "#882700",
    "--action-tertiary-fg-selected": "#882700",
    "--action-tertiary-fg-inverse": "#f6f5f5",
    "--destructive-bg": "#fff2f0",
    "--destructive-bg-alt": "#ffe0db",
    "--destructive-bg-hover": "#ffe0db",
    "--destructive-bg-active": "#6b1614",
    "--destructive-bg-selected": "#6b1614",
    "--destructive-bg-disabled": "#d6d3d3",
    "--destructive-bg-inverse": "#6b1614",
    "--destructive-border": "#d73532",
    "--destructive-border-alt": "#6b1614",
    "--destructive-border-disabled": "#ffe0db",
    "--destructive-border-inverse": "#fff2f0",
    "--destructive-fg": "#901315",
    "--destructive-fg-alt": "#121111",
    "--destructive-fg-disabled": "#9d9795",
    "--destructive-fg-active": "#f6f5f5",
    "--destructive-fg-selected": "#f6f5f5",
    "--destructive-fg-inverse": "#fff2f0",
    "--positive-bg": "#edf9ee",
    "--positive-bg-alt": "#d9efda",
    "--positive-bg-inverse": "#004815",
    "--positive-border": "#009434",
    "--positive-border-alt": "#004815",
    "--positive-border-inverse": "#edf9ee",
    "--positive-fg": "#005e1e",
    "--positive-fg-alt": "#121111",
    "--positive-fg-inverse": "#edf9ee",
    "--cautionary-bg": "#fdf4e7",
    "--cautionary-bg-alt": "#f6e5ce",
    "--cautionary-bg-inverse": "#513400",
    "--cautionary-border": "#a46e00",
    "--cautionary-border-alt": "#513400",
    "--cautionary-border-inverse": "#fdf4e7",
    "--cautionary-fg": "#694500",
    "--cautionary-fg-alt": "#121111",
    "--cautionary-fg-inverse": "#fdf4e7",
    "--informative-bg": "#eff6ff",
    "--informative-bg-alt": "#d9eaff",
    "--informative-bg-inverse": "#003974",
    "--informative-border": "#0078e7",
    "--informative-border-alt": "#003974",
    "--informative-border-inverse": "#eff6ff",
    "--informative-fg": "#004b96",
    "--informative-fg-alt": "#121111",
    "--informative-fg-inverse": "#eff6ff",
    "--important-bg": "#fff2ee",
    "--important-bg-alt": "#ffe0d7",
    "--important-bg-inverse": "#691c00",
    "--important-border": "#d24100",
    "--important-border-alt": "#691c00",
    "--important-border-inverse": "#fff2ee",
    "--important-fg": "#882700",
    "--important-fg-alt": "#121111",
    "--important-fg-inverse": "#fff2ee",
    "--dataviz-1-main": "#d24100",
    "--dataviz-1-tint": "#ffe0d7",
    "--dataviz-1-shade": "#691c00",
    "--dataviz-2-main": "#0078e7",
    "--dataviz-2-tint": "#d9eaff",
    "--dataviz-2-shade": "#003974",
    "--dataviz-3-main": "#009434",
    "--dataviz-3-tint": "#d9efda",
    "--dataviz-3-shade": "#004815",
    "--dataviz-4-main": "#ac3400",
    "--dataviz-4-tint": "#ffc5b3",
    "--dataviz-4-shade": "#481100",
    "--dataviz-5-main": "#a46e00",
    "--dataviz-5-tint": "#f6e5ce",
    "--dataviz-5-shade": "#513400",
    "--dataviz-6-main": "#d73532",
    "--dataviz-6-tint": "#ffe0db",
    "--dataviz-6-shade": "#6b1614",
    "--dataviz-7-main": "#007829",
    "--dataviz-7-tint": "#b7e1ba",
    "--dataviz-7-shade": "#01300c",
    "--dataviz-8-main": "#0061be",
    "--dataviz-8-tint": "#b8d7ff",
    "--dataviz-8-shade": "#05264c",
  },
});

Installation

The package is a dev dependency — you generate tokens at build time (or once, by hand) and ship the CSS.

CLI

The eluan-theme binary writes primitive and semantic token JSON, the theme CSS, and a preview HTML file you can open to eyeball the result.

Programmatic API

generateTheme is synchronous and safe to call from a client component — this page is the proof. Lower-level helpers are exported too: validateAccents, generateScale, checkContrast, autoCorrect, compileThemeCSS, and compileCreateThemeTokens.

Accents beyond the first generate additional --color-secondary-* and --color-tertiary-* ramps you can reference directly; the semantic tokens are all derived from the first accent. Pass a custom curve or systemHues to reshape the lightness ladder or move the error / warning / success / info hues.

Multi-brand systems, exports, and Figma sync — coming later.