feat(studio): upgrade Studio to Tailwind CSS v4 - #3618
Draft
miguel-heygen wants to merge 1 commit into
Draft
Conversation
Studio moves from Tailwind 3.4 to 4.3.3, loaded by @tailwindcss/vite instead of PostCSS. The JS config becomes an @theme block in studio.css carrying the same studio.* and panel.* colors, with explicit @source globs so the gitignored data/projects tree is never scanned. The official upgrade tool renamed the utilities, but it rewrites bare words inside any string, so its edits to event names, CSS property names, filter function names, status enum values and prose were reverted by hand. Nothing in Studio relied on the v3 default border color, ring width or ring color, so the tool's compatibility shim was dropped rather than kept.
This was referenced Sep 4, 2026
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.
Lands unit U14 (Tailwind v4) of the Studio design-system foundation. No ratchet yet; no color literal added. One PR over the usual size guideline: a half-migrated Tailwind tree does not build, so there is no seam to split at.
Lands U14 of the Studio design-system plan. Ratchet delta: none (the hex ratchet lands in a later unit).
What
Studio moves from Tailwind CSS 3.4 to 4.3.3, loaded through
@tailwindcss/viteinstead of PostCSS.packages/studio/tailwind.config.jsandpackages/studio/postcss.config.jsare deleted. The JS config'sstudio.*andpanel.*palettes become an@themeblock insrc/styles/studio.css, byte-for-byte the same hex values.@import "tailwindcss" source(none)plus explicit@sourceglobs forsrc/**/*.{ts,tsx}andindex.html. Automatic source detection would walk up to the repository root and scandata/projects, which is gitignored user content rather than Studio markup.autoprefixerandpostcssare dropped from Studio's dev dependencies;@tailwindcss/viteis added.The published
./tailwind-presetsubpath is untouched:src/styles/tailwind-preset.shared.jsandtailwind-preset.tsstill exist and still build todist/styles/tailwind-preset.js. Turning it into a deprecated shim is a later unit's job.Why
Tailwind v4 is a prerequisite for the token work in this plan. It makes the token file CSS-first, emits every token as a real CSS custom property, and handles alpha on any color format, which removes most of the hand-built plumbing the token unit would otherwise need.
How
The official
@tailwindcss/upgradetool did the utility renames. Its diff was then reviewed by hand, which mattered more than expected: the tool rewrites bare words inside any string literal, not only inside class attributes. Left as-is it would have shipped these defects, all of which were reverted:removeEventListener("blur", ...)becameremoveEventListener("blur-sm", ...)in five files, so focus-loss cleanup would never have fired.element.style.setProperty("flex-grow"/"flex-shrink", ...)becamesetProperty("grow"/"shrink", ...)in the DOM-editing patch and snapshot paths, which are no-op CSS property names.getCssFilterFunctionPx(styles.filter, "blur")became"blur-sm", breaking the inspector's blur sliders.FrameStatusvalue"outline"became"outline-solid"in the storyboard status order and its consumers."Layer blur"), test names and code comments were rewritten as prose.Every one of these was outside a
className. The rule that caught them: a change the tool makes on a line with noclassNameon it is a false positive until proven otherwise.Default changes the codemod did not make, and what was done about each
gray-200tocurrentColorborder-colorback togray-200on*. An audit of every string literal carrying a bareborder/border-<side>found 15 hits: all are eitherborder-0, or prose in test names, or already carry an explicit color (an inlineborderColor, or a variant class supplyingborder-emerald-700/border-sky-500/border-neutral-700). Everyborder:declaration instudio.cssnames its own color.3to1, ring color tocurrentColorringutility. Everyring-*usage names both a width (ring-1,ring-2) and a color. The four grep hits for a bareringare all the English word in comments.divide-*default colordivide-y, and it carriesdivide-panel-border-input/60.outline-nonesemanticsoutline-hidden(79 bare, 14 under afocus:variant); bareoutlinemigrated tooutline-solid.roundedtorounded-sm,rounded-smtorounded-xs,shadowtoshadow-sm,drop-shadowtodrop-shadow-sm,blurtoblur-sm,backdrop-blur-smtobackdrop-blur-xs.bg-opacity-*removed@apply/ the v3theme()function in CSSstudio.cssuses neither.ButtonandIconButtonalready setcursor-pointerexplicitly.field-sizing: contentis emitted.compile()API;postcssis no longer a Studio dependency.Test plan
Studio's dev server, suite, typecheck and build all run against the new pipeline.
typecheck: clean.build: clean, including thetailwind-presetsubpath still emitting todist/styles/tailwind-preset.js.oxlint: 0 warnings, 0 errors.oxfmt --check: clean (7 files were reformatted because the longer v4 class names push lines past the print width).PASS: studio loaded with schema-valid API fixtures and no runtime errors..bg-studio-accent,.bg-panel-surface,.text-panel-text-2,.border-panel-border-input,.bg-panel-bg-insetare all present, and--color-panel-surface:#18181bis emitted as a real custom property. Alpha on a theme color folds correctly (.bg-studio-accent\/3compiles to#3ce6ac08).Screenshot and computed-style capture
The plan's capture script was run on the base branch and on this branch.
Screenshots. Reporting a raw before/after difference here would be misleading, because the capture is not deterministic: two runs of the same base-branch code differ by SSIM 0.923 / 0.922 / 0.931 on three of the six states, because the preview canvas and the composition list each race the screenshot. Measured against that noise floor, every before/after delta is at or below same-code run-to-run variation:
No screenshot difference is attributable to the upgrade.
Computed-style table. Four of the five rows are identical. Two cells changed, both reproducible across repeated runs:
rgba(255, 255, 255, 0.08)oklab(0.999994 0.0000455678 0.0000200868 / 0.08)bg-white/8throughcolor-mix(in oklab, ...)because the color comes from a custom property. That oklab triple is white. The screenshot containing this button matches within the noise floor.14px11pxThe Renders Export font size
That button carries two competing font-size utilities:
text-smfrom the sharedButton's size styles, andtext-[11px]from the caller'sclassName. v3 emitted the named scale after the arbitrary value, sotext-smwon and the control rendered at 14px. v4 emits arbitrary values after the named scale, so the caller'stext-[11px]now wins.This is not a regression introduced by the migration: it is the same latent defect the plan already names as the reason for adopting a class-merging helper, and v4 resolves it toward what the caller asked for.
A DOM-wide sweep across four panel states looked for every element carrying two competing utilities of the same property. This button is the only one. (Three other hits were a false positive of the scan:
text-[#3CE6AC]-style colors matching a font-size pattern.) So the blast radius of v4's reordering is exactly one control, and it is the control a later unit is already scheduled to fix.Not covered
@themeblock is a direct translation of the old JS palette. Semantic tokens, the type scale, radius/shadow/easing tokens, and clearing Tailwind's default palette with--color-*: initialare a later unit's scope and are deliberately absent../tailwind-presetJS export still exports the raw v3-shaped preset object. Turning it into a deprecated shim is a later unit.rounded-buttonandshadow-btn-primaryare referenced by the sharedButtonbut are not real utilities and emit nothing, on v3 and on v4 alike. Left as-is: they are pre-existing and the token unit owns them.