feat(studio): a light and dark theme toggle in the header that remembers the choice - #4839
Merged
Merged
Conversation
miguel-heygen
force-pushed
the
feat/studio-theme-toggle
branch
4 times, most recently
from
October 1, 2026 12:51
8fe20db to
1b974d2
Compare
miguel-heygen
force-pushed
the
feat/studio-theme-toggle
branch
from
October 1, 2026 14:22
33ef964 to
10b8115
Compare
miguel-heygen
marked this pull request as ready for review
October 1, 2026 14:23
…ers the choice The sun and moon button flips Studio between Graphite light and dark with a circular reveal, saves the choice, and follows the system until one is made.
…ows the theme on screen A host that embeds StudioApp gets no toggle; the button reads the document's theme, the reveal drops the default crossfade, and reduced motion skips it.
…e default for the toggle The crossfade rule applies only while the toggle's own transition runs, StudioApp passes the header's single default through, and the reveal's reduced-motion and keyboard paths are tested.
Only the latest reveal clears the document marker; tests pin the marker while a reveal runs and the circle's radius.
… plain functions Simplify pass: one icon props object for the sun and moon, no useCallback around a handler nothing memoises, cannotReveal named for what it answers, the test reuses the theme reader.
Edit accuracy: 557 passing here, 557 on the base branchThe gate passes. |
…xt; tests parse index.html App.tsx stays at main's size: main.tsx provides ShowThemeToggle and the header reads it, default off for hosts. The boot-script tests read index.html through DOMParser instead of an HTML regex.
miguel-heygen
force-pushed
the
feat/studio-theme-toggle
branch
from
October 1, 2026 14:58
10b8115 to
1c4be8a
Compare
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.
What changes for a person using Studio
A sun and moon button in Studio's header flips between light and dark, with a circular reveal from the button, the same control as hyperframes.dev's theme toggle. Studio opens light the first time; after that the saved choice comes back on the next load from the first frame. The player stays dark in both. With reduced motion on, the theme switches without the reveal.
How
utils/studioTheme.tsowns the rule for Studio's own app: the saved choice (themein Studio's existinghf-studio-ui-preferences), else light. Light isdata-theme="paper"on the document element, the hooktheme.csskeys on; dark is no attribute.index.htmlapplies the same rule before the first paint, so the saved theme paints from the first frame.studioTheme.test.tsxreads that script out of the parsedindex.htmland runs it against every saved value (including junk,nulland unparsable storage) and holds it to the owner.ThemeToggleis the header's ghost icon button with a tooltip, before Window. While its own transition runs it marks the document element, and only then is the browser's default crossfade switched off, so other view transitions on the page keep theirs.ShowThemeToggle(a context that defaults off), and only Studio's ownmain.tsxprovides it. A host that embedsStudioAppgets no toggle, and Studio never writesdata-themefor it.App.tsxis unchanged.Before
After
The repo's
product-promoexample at 1280x800, 1440x900 and 1920x1080.Verification
studioTheme.test.tsx: the boot script equals the owner for saved light, dark, nothing, junk,null, unparsable and non-object storage; first run is light; boot runs before the app module; a click flips and saves, a second click flips back; the button follows a theme set by someone else. The reveal tests stub view transitions: a keyboard press grows the circle from the button's centre to the far corner, the document is marked while the reveal runs and cleared after, a second click mid-reveal keeps the marker until its own reveal ends, and reduced motion switches with no transition. Making the button read storage, ignoring reduced motion, using the click point for a keyboard press, never setting or never clearing the marker, or letting an earlier reveal clear a later one's marker each fails its test.StudioHeader.dom.test.tsx: a header mounted with no provider (as a host mounts it) shows no toggle and leaves a host'sdata-themealone; inside the provider it shows the toggle. Defaulting the context to on fails the first.studioUiPreferences.test.tspass.tsc --noEmiton packages/studio: no errors. oxlint and oxfmt clean on the changed files.product-promoexample): the first load is light, a click reveals dark from the button (the only animation is the circle's clip-path; the crossfade is off while the marker is set, and the marker is gone after) and removesdata-theme, and a reload boots straight into dark.