feat(styles): make the built-in auth UI themeable with CSS custom properties - #129
Merged
Conversation
…perties Every colour in the bundled screens now reads from a --seamless-* custom property with the previous literal as its fallback. Consumers can match the auth UI to their brand by setting variables on :root or on any ancestor of AuthRoutes, with no provider prop and no JavaScript surface. The 118 hardcoded literals collapse onto 16 semantic tokens: four accent tokens, three surface tokens, four neutral tokens, three status tokens, and two effect tokens (overlay and shadow). Near-duplicate shades share a token while each declaration keeps its own original value as the fallback, so a single override unifies the role without shifting the defaults. Four values stay hardcoded. The magic-link pulse ring and the success check disc are translucent tints sitting under an icon, so an override that drops the alpha would paint an opaque disc over the icon it frames. The three transparent keywords are intentionally invisible rather than themeable. This is opt-in and non-breaking. Stripping the var() wrappers from the new files yields output byte-identical to the previous version, so applications that set nothing render exactly as before.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
src/styles/*.csscarried 118 hardcoded colour literals (42 distinct) and zero custom properties, 28 of them blue. An application embedding<AuthRoutes />got a blue login form regardless of its own brand palette. This surfaced while building an app generator on top ofseamless-templates: the generated app had a deep-spruce and orange palette everywhere except the login form, which stayed blue and looked pasted in.Change
Every colour declaration now reads from a
--seamless-*custom property with the previous literal as its fallback. Consumers theme the auth UI by setting variables on:rootor on any ancestor ofAuthRoutes. There is no provider prop and no JavaScript surface.The literals collapse onto 16 semantic tokens named for their role:
accent,accent-hover,accent-contrast,accent-softsurface,surface-raised,surface-hoverborder,text,text-muted,disableddanger,success,warningoverlay,shadowNear-duplicate shades share a token while each declaration keeps its own original value as the fallback, so one override unifies the role without shifting today's defaults. Greys split by role rather than by value:
#4b5563maps toborderwhere it is a border and tosurface-raisedwhere it is an input background.Three tokens push the count past a tighter 12, and each prevents a regression for consumers who do theme. Without
surface-hoverthe MFA method buttons lose their hover state entirely, since base and hover would both resolve tosurface-raised. Withoutaccent-softlinks render in the brand accent, which is often unreadable on a dark card. Withoutdisableda disabled submit button becomes indistinguishable from an active one.Non-breaking
Stripping the
var()wrappers from the new files yields output byte-identical to the previous version across all 10 modules. That simultaneously proves the change is colour only (no layout, spacing, typography, or class changes) and that every fallback is exactly the value it replaced. Applications that set nothing render as before.The built CSS in
dist/index.jsmatches the previous build modulovar()indirection, with one benign delta: cssnano can no longer collapse the passkey spinner's four border longhands intoborder-color: #3b82f6 transparent, so it emits the longhands. Same computed result, a few bytes larger.Deliberately left hardcoded
rgba(99, 102, 241, 0.15)(magic-link pulse ring) andrgba(16, 185, 129, 0.15)(success check disc). Both are translucent tints sitting directly under an icon, so an override that drops the alpha would paint an opaque disc over the icon it is meant to frame. The otherrgba()values have nothing on top and were safe to tokenise.transparentkeywords (spinner border sides, secondary button background). Intentionally invisible rather than themeable.Judgement calls worth a look
#059669), the only non-blue primary button. It maps toaccentandaccent-hoverrather thansuccess, so a consumer's brand colour styles it like every other primary action. The default stays green.registerPasskey's.loadingand.supportedpanels are light-on-light islands in an otherwise dark UI. They map to the genericsurfaceandtextpair so they join the rest of the theme on override instead of staying light. That drops the green tint on the supported panel for theming consumers. The alternative was two single-use tokens.Dark mode
There were no
prefers-color-schemeblocks anywhere in the package, so there was no existing behaviour to preserve. The README points consumers at a media query if they want the auth UI to follow the system theme.Docs
A Theming The Built-In UI section in the README covers the
:rootoverride example, a wrapper-scoped variant, the full token table with defaults, and notes on the multi-default rows, the--seamless-shadowalpha collapse, the light-surface and white-text gotcha, and the two untokenised tints.Checks
npm run typecheck,npm run lint,npm run format:check,npm test(290 passed, 32 suites), andnpm run buildall pass.