feat(useSchema): expose the ambient schema and contrast tier to JS - #1362
Merged
tenphi merged 5 commits intoAug 25, 2026
Merged
Conversation
`@dark` / `@hc` answer the ambient viewing conditions in CSS, but two places cannot use a state map: surfaces that take values rather than CSS (a Vega spec, a CodeMirror/Monaco theme, an iframe) and controls whose value *is* the condition. Consumers re-implemented the expansion instead — Cloud's `useResolvedScheme()` and the App Theme page's own observer, and the kit's own theme builder story too. `useScheme()` / `useHighContrast()` (plus `resolveScheme()`, `resolveHighContrast()`, `subscribeScheme()` outside React) read the same conditions from JS. The new module owns the `@dark` / `@hc` strings that `Root` registers, so the CSS and JS answers cannot drift apart. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 5459f4c The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📦 NPM canary releaseDeployed canary version 0.0.0-canary-8c0f42f. |
Contributor
🧪 Storybook is successfully deployed!
|
Contributor
🏋️ Size limit report
Compared against main at 09d0a20 — run 32867273009, 2026-08-25T15:40:37Z.To see which modules changed, download the size-limit-statoscope-report artifact from this run and open report.html. |
One word for one concept: the attribute is `data-schema` and the state is `@root(schema=…)`, so the hooks follow it — `useSchema()`, `resolveSchema()`, `subscribeSchema()`, `ColorSchema`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BREAKING CHANGE: `renderColorTokens`/`renderPaletteTokens` take `schema` instead of `scheme`, `<CubeLogo>`/`<CubeFullLogo>` take a `schema` prop, and the probe uses `tokenOptions.schema` / `--schema`. One word for one concept: the attribute is `data-schema` and the state is `@root(schema=…)`, so the API, the internals, the stories and the docs follow it. `prefers-color-scheme` is untouched — that name is the CSS media feature's, not ours. `SchemeIcon` is untouched too: it wraps a sitemap drawing and means the other thing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Conflicts were the markdown unwrap (#1345) meeting the `scheme` → `schema` rename: took main's unwrapped docs and re-applied the rename, then re-added the two new doc sections unwrapped. Also dedupes the watcher: `resolve.ts` (#1351) had grown its own appearance store over the same two attributes and two media queries, so it now subscribes through `subscribeSchema()`, which owns the definition. Its stricter guards (no `matchMedia`, no `MutationObserver`) moved into that module with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tenphi
deleted the
andrew/cub-4118-expose-the-ambient-scheme-and-high-contrast-to-js-useschema
branch
August 25, 2026 17:14
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.
Describe changes
Exposes the ambient viewing conditions — the color scheme and the contrast tier — to JS. Closes CUB-4118.
@dark/@hcalready answer this in CSS, and for styling that remains the answer ({ '': light, '@dark': dark, '@hc': hc }repaints on a scheme flip with no re-render). Two cases a state map cannot serve:Consumers re-implement the expansion today: Cloud's
useResolvedScheme()(packages/console-ui/src/styles/adaptive-color.ts) hand-builds it fromusePreferences()+useSyncExternalStore, andAppThemeCustomizationPage.tsxadds aprefers-contrast: morelistener plus aMutationObserverfor the high-contrast half. Both are copies of a definition the kit owns. So was the kit's own theme builder story, which this PR deletes in favour of the new hooks.API
Outside React:
resolveSchema(),resolveHighContrast(),subscribeSchema(listener)(fires on a change to either condition). Type:ColorSchema.Merged
mainin (18 commits). Two things came out of that: the conflicts were#1345's markdown unwrap meeting this rename — took main's unwrapped docs and re-applied it — andresolve.tsfrom #1351 had meanwhile grown its own appearance store over the same two attributes and two media queries. It now subscribes throughsubscribeSchema(), so there is one observer for the document rather than one per concern; its stricter guards (nomatchMedia, noMutationObserver) moved into the owning module with it.New module
src/utils/react/useSchema.tsis the single owner of the definition — it builds the@dark/@hcstrings thatRootregisters viasetGlobalPredefinedStates()and reads the same two conditions from JS, so the CSS and JS answers cannot drift apart.Implementation notes:
MutationObserver(data-schema/data-contraston<html>) plus the two media-query listeners, refcounted across all subscribers and torn down when the last one unmounts.useSyncExternalStoreneeds no memoization and never warns about an uncached snapshot. Under SSR the hooks render'light'/falseand re-render after hydration.!@root(schema)), so<html data-schema="light">stays light on a dark OS, and a present-but-unknown value reads as light rather than falling through to the preference.hovered,pressed) with ambient ones, and only the ambient half is answerable without an element. See the ticket for why the generic version was rejected.Naming —
schema, everywhere (breaking)One word for one concept: the attribute is
data-schemaand the state is@root(schema=…), so the whole kit follows it now. Beyond the new hooks, the last commit renames three pre-existing public surfaces, with no aliases (per the repo's no-back-compat rule):renderColorTokens({ scheme }),renderPaletteTokens({ scheme }),RenderPaletteOptions.scheme{ schema }<CubeLogo scheme="dark">,<CubeFullLogo scheme>schematokenOptions.scheme, CLIpnpm probe --schemetokenOptions.schema,--schemaInternals, stories, tests and docs follow (
SCHEMA_SWAP,withColorSchema,colorSchemaBridge, prose). Two deliberate exceptions:prefers-color-schemekeeps its spelling (that name belongs to the CSS media feature, not us), andSchemeIconis untouched — it wraps a sitemap drawing and means the other thing.Cloud call sites to update:
renderColorTokens/renderPaletteTokensoptions, any<CubeLogo scheme>, andyarn probe --scheme.--schema hcstill means light + high contrast.Checklist
Closes: CUB-4118
Other information
Verification
src/utils/react/useSchema.test.tsx): media-query path, attribute-wins-over-preference, unknown-value-reads-as-light, live attribute flip, live preference change, and watcher re-attach after the last subscriber leaves. Full suite: 2100 passed / 1 skipped.Getting Started/Theming→ Theme builder): with no attribute and a dark-preferring OS the switches came up Dark / Normal; settingdata-schema="light"+data-contrast="high"flipped them to Light / High contrast, no console errors — both resolution paths through the hooks in a real browser.pnpm size: 512.19 kB / 515 kB and 121.81 kB / 125 kB.resolve.test.tswhich exercises the rewired watcher),pnpm probe tokens --schema darkworks,pnpm audit-docs --component=CubeLogoreports the same pre-existing findings asmain(stroke,isChecked,scrollMargin) and nothing about the renamed prop,pnpm audit-defaultsleaves the registry unchanged.pnpm chromatic:checkfails identically onmain(341 modules vs a 170 budget) — pre-existing, not from this branch.Unrelated finding:
pnpm sizefails on a staledist/— a build predating the tasty 3.1.0 bump still importsTastyBatchProvider, which 3.1.0 no longer exports.pnpm buildfirst and it passes; nothing to fix in source.No new stories: the hooks have no visual surface of their own, and the theme builder story now exercises them.
Note
Medium Risk
Breaking renames across palette rendering, logos, and probe CLI require consumer updates; new ambient-condition hooks are additive but affect how
@dark/@hcare defined at startup viaRoot.Overview
Adds
useSchema(),useHighContrast(), and non-ReactresolveSchema()/resolveHighContrast()/subscribeSchema()so JS can read the same ambient light/dark and high-contrast conditions as the@dark/@hcstates (<html data-schema>/data-contrastfirst, thenprefers-*).Rootnow registersAMBIENT_PREDEFINED_STATESfromuseSchema.tsinstead of duplicating those strings, so CSS and JS stay aligned.Breaking: renames
scheme→schemaeverywhere public with no aliases —renderColorTokens({ schema }),<CubeLogo schema>, probe--schema, StorybookwithColorSchema/colorSchemaBridge, plus docs and internal naming. The theme builder drops its hand-rolled document watcher in favor of the new hooks.Reviewed by Cursor Bugbot for commit 5459f4c. Bugbot is set up for automated code reviews on this repo. Configure here.