') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); fix(web-shared): match Geist Button hover, focus, and radius by mitul-s · Pull Request #2143 · vercel/workflow · GitHub
Skip to content

fix(web-shared): match Geist Button hover, focus, and radius - #2143

Merged
mitul-s merged 1 commit into
mainfrom
ms/geist-button-states
May 28, 2026
Merged

fix(web-shared): match Geist Button hover, focus, and radius#2143
mitul-s merged 1 commit into
mainfrom
ms/geist-button-states

Conversation

@mitul-s

Copy link
Copy Markdown
Contributor

Summary

Brings the shared Button (packages/web-shared/src/components/ui/button.tsx) to 1:1 with the Geist Button (verified against the Geist source: packages/geist/src/components/button/button.module.css). Fixes three issues, most visibly on the encrypted-data "Decrypt" overlay (EncryptedDataBlock):

  • Hover (was broken): the inverted/default button drove its dark-mode hover with a dark-theme:hover: class, but dark-theme is not a registered Tailwind variant in our setup (theming is done by flipping --ds-* tokens), so the class compiled to nothing. In dark mode the white button fell back to the hardcoded light hover (hsl(0,0%,22%)) and turned dark gray; when imported into apps that supply their own Geist tokens (e.g. vercel/front), an earlier token-based attempt resolved to nothing and the background went transparent. Now uses Geist's literal hover fallbacks (hsl(0,0%,22%) / hsl(0,0%,80%)) driven by arbitrary ancestor-theme variants ([.dark-theme_&]: and [[data-theme=dark]_&]:) — no token dependency, so it resolves in any consumer.
  • Focus (was missing): added Geist's focus ring (outline: 2px solid var(--ds-focus-color) + 2px offset). Also defines --ds-focus-color (blue-700 / blue-900) in web-shared/styles.css, which was referenced but undefined (also fixes icon-button's colorless focus outline).
  • Radius:xs (24px) now uses Geist's tiny 4px radius instead of inheriting rounded-md (6px).

Tailwind v3 + v4 compatibility

web-shared is consumed by both TW3 and TW4 apps, so the focus ring uses arbitrary properties ([outline:...]) rather than the bare outline / outline-[var()] utilities (which differ between versions). Verified by compiling the exact class string through Tailwind 3.3.6 and 4.2.4 — both emit the correct .dark-theme …:hover, [data-theme=dark] …:hover, focus-ring, and border-radius:4px rules, with literal hsl() fallbacks present (so the background can never resolve to transparent).

Test plan

  • pnpm build for @workflow/web-shared (button/styles changes typecheck)
  • Compiled button classes through Tailwind v3.3.6 and v4.2.4 — output verified
  • Visual check of idle / hover / focus / decrypted states in dark mode
  • Confirm hover/focus render correctly when web-shared is imported into vercel/front

Note

CI's pnpm turbo run build may flag a pre-existing, unrelatedtsc error on main: attribute-panel.tsx:423 declares an attributes display fn whose key isn't in the AttributeKey type (TS2353). It is independent of this change (resolved separately on the ms/trace-search branch). Not touched here to keep this PR focused.

🤖 Generated with Claude Code

The shared Button's dark-mode hover relied on an unregistered `dark-theme:`
Tailwind variant, so the inverted (default) button lost its hover style — and
the background resolved to transparent when consumed by apps that supply their
own Geist tokens (e.g. vercel/front). Use Geist's literal hover fallbacks
driven by arbitrary ancestor-theme variants instead, render the previously
missing focus-visible ring, and apply Geist's 4px tiny radius to the xs size.
Authored to compile under both Tailwind v3 and v4.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@mitul-s
mitul-s requested a review from a team as a code ownerMay 28, 2026 20:38
@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e08f2ae

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 16 packages
NameType
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/cliPatch
workflowPatch
@workflow/world-testingPatch
@workflow/corePatch
@workflow/buildersPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/vitestPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/nuxtPatch

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

@vercel

vercelBot commented May 28, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented May 28, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production125502191474
✅ 💻 Local Development165702191876
✅ 📦 Local Production165702191876
✅ 🐘 Local Postgres165702191876
✅ 🪟 Windows13400134
✅ 📋 Other7620176938
Total7122010528174

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro108026
✅ example108026
✅ express108026
✅ fastify108026
✅ hono108026
✅ nextjs-turbopack13202
✅ nextjs-webpack13202
✅ nitro108026
✅ nuxt108026
✅ sveltekit12707
✅ vite108026
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable109025
✅ express-stable109025
✅ fastify-stable109025
✅ hono-stable109025
✅ nextjs-turbopack-canary115019
✅ nextjs-turbopack-stable-lazy-discovery-disabled13400
✅ nextjs-turbopack-stable-lazy-discovery-enabled13400
✅ nextjs-webpack-canary115019
✅ nextjs-webpack-stable-lazy-discovery-disabled13400
✅ nextjs-webpack-stable-lazy-discovery-enabled13400
✅ nitro-stable109025
✅ nuxt-stable109025
✅ sveltekit-stable12806
✅ vite-stable109025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable109025
✅ express-stable109025
✅ fastify-stable109025
✅ hono-stable109025
✅ nextjs-turbopack-canary115019
✅ nextjs-turbopack-stable-lazy-discovery-disabled13400
✅ nextjs-turbopack-stable-lazy-discovery-enabled13400
✅ nextjs-webpack-canary115019
✅ nextjs-webpack-stable-lazy-discovery-disabled13400
✅ nextjs-webpack-stable-lazy-discovery-enabled13400
✅ nitro-stable109025
✅ nuxt-stable109025
✅ sveltekit-stable12806
✅ vite-stable109025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable109025
✅ express-stable109025
✅ fastify-stable109025
✅ hono-stable109025
✅ nextjs-turbopack-canary115019
✅ nextjs-turbopack-stable-lazy-discovery-disabled13400
✅ nextjs-turbopack-stable-lazy-discovery-enabled13400
✅ nextjs-webpack-canary115019
✅ nextjs-webpack-stable-lazy-discovery-disabled13400
✅ nextjs-webpack-stable-lazy-discovery-enabled13400
✅ nitro-stable109025
✅ nuxt-stable109025
✅ sveltekit-stable12806
✅ vite-stable109025
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack13400
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable109025
✅ e2e-local-dev-tanstack-start-109025
✅ e2e-local-postgres-nest-stable109025
✅ e2e-local-postgres-tanstack-start-109025
✅ e2e-local-prod-nest-stable109025
✅ e2e-local-prod-tanstack-start-109025
✅ e2e-vercel-prod-tanstack-start108026

📋 View full workflow run

@github-actions

github-actionsBot commented May 28, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.039s (-10.0% 🟢)1.005s (~)0.966s101.00x
💻 LocalExpress0.044s (-1.6%)1.006s (~)0.962s101.12x
💻 LocalNext.js (Turbopack)0.051s1.005s0.954s101.30x
🐘 PostgresExpress0.062s (+7.4% 🔺)1.013s (~)0.950s101.61x
🐘 PostgresNitro0.064s (-33.3% 🟢)1.011s (-3.0%)0.948s101.64x
🐘 PostgresNext.js (Turbopack)0.067s1.011s0.944s101.73x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express0.272s (+15.7% 🔺)2.006s (-6.1% 🟢)1.733s101.00x
▲ VercelNext.js (Turbopack)0.286s (+13.6% 🔺)2.469s (+5.8% 🔺)2.183s101.05x
▲ VercelNitro0.286s (-30.2% 🟢)2.658s (+5.9% 🔺)2.372s101.05x

🔍 Observability: Express | Next.js (Turbopack) | Nitro

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.091s (-3.1%)2.006s (~)0.915s101.00x
💻 LocalNitro1.092s (-3.5%)2.007s (~)0.915s101.00x
💻 LocalNext.js (Turbopack)1.099s2.005s0.907s101.01x
🐘 PostgresExpress1.101s (-3.9%)2.011s (~)0.910s101.01x
🐘 PostgresNitro1.106s (-3.0%)2.010s (~)0.904s101.01x
🐘 PostgresNext.js (Turbopack)1.147s2.008s0.861s101.05x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.609s (-14.2% 🟢)3.201s (-15.9% 🟢)1.593s101.00x
▲ VercelNitro1.767s (-54.6% 🟢)3.548s (-40.0% 🟢)1.782s101.10x
▲ VercelNext.js (Turbopack)1.782s (-12.4% 🟢)4.124s (+7.7% 🔺)2.342s101.11x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro10.499s (-4.1%)11.021s (~)0.522s31.00x
💻 LocalExpress10.522s (-3.7%)11.023s (~)0.501s31.00x
🐘 PostgresNitro10.555s (-2.9%)11.022s (~)0.466s31.01x
🐘 PostgresExpress10.587s (-3.4%)11.021s (~)0.434s31.01x
💻 LocalNext.js (Turbopack)10.620s11.021s0.401s31.01x
🐘 PostgresNext.js (Turbopack)10.881s11.020s0.139s31.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)13.594s (-21.5% 🟢)15.402s (-20.6% 🟢)1.808s21.00x
▲ VercelExpress13.815s (-18.6% 🟢)15.331s (-23.4% 🟢)1.517s31.02x
▲ VercelNitro14.802s (-37.6% 🟢)16.567s (-34.0% 🟢)1.765s21.09x

🔍 Observability: Next.js (Turbopack) | Express | Nitro

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro13.691s (-9.1% 🟢)14.027s (-12.5% 🟢)0.336s51.00x
💻 LocalExpress13.707s (-8.4% 🟢)14.029s (-6.7% 🟢)0.322s51.00x
🐘 PostgresNitro13.726s (-6.0% 🟢)14.018s (-6.7% 🟢)0.291s51.00x
🐘 PostgresExpress13.744s (-5.8% 🟢)14.020s (-6.7% 🟢)0.277s51.00x
💻 LocalNext.js (Turbopack)13.922s14.023s0.101s51.02x
🐘 PostgresNext.js (Turbopack)14.501s15.018s0.517s41.06x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express22.733s (-54.8% 🟢)24.583s (-53.2% 🟢)1.850s31.00x
▲ VercelNitro23.339s (-63.8% 🟢)25.144s (-62.3% 🟢)1.805s31.03x
▲ VercelNext.js (Turbopack)23.645s (-55.0% 🟢)26.823s (-50.9% 🟢)3.178s31.04x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro12.325s (-26.6% 🟢)13.025s (-23.5% 🟢)0.699s71.00x
💻 LocalExpress12.395s (-25.3% 🟢)13.025s (-23.5% 🟢)0.630s71.01x
🐘 PostgresExpress12.428s (-11.3% 🟢)13.019s (-10.8% 🟢)0.591s71.01x
🐘 PostgresNitro12.652s (-9.4% 🟢)13.020s (-9.0% 🟢)0.368s71.03x
💻 LocalNext.js (Turbopack)12.813s13.165s0.352s71.04x
🐘 PostgresNext.js (Turbopack)13.605s14.018s0.413s71.10x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro28.306s (-93.3% 🟢)30.504s (-92.8% 🟢)2.198s31.00x
▲ VercelExpress28.994s (-76.1% 🟢)30.896s (-75.0% 🟢)1.902s31.02x
▲ VercelNext.js (Turbopack)31.012s (-92.1% 🟢)33.853s (-91.4% 🟢)2.841s31.10x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.194s (-6.3% 🟢)2.008s (~)0.814s151.00x
🐘 PostgresExpress1.195s (-5.2% 🟢)2.008s (~)0.813s151.00x
💻 LocalExpress1.225s (-17.7% 🟢)2.006s (~)0.781s151.03x
🐘 PostgresNext.js (Turbopack)1.245s2.007s0.762s151.04x
💻 LocalNitro1.299s (-20.4% 🟢)2.006s (-3.3%)0.707s151.09x
💻 LocalNext.js (Turbopack)1.338s2.005s0.667s151.12x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.515s (-12.1% 🟢)3.810s (-17.6% 🟢)1.295s81.00x
▲ VercelNitro2.714s (-3.7%)4.649s (+7.6% 🔺)1.936s71.08x
▲ VercelNext.js (Turbopack)3.063s (-9.9% 🟢)4.759s (-3.5%)1.696s71.22x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.242s (-47.4% 🟢)2.008s (-33.3% 🟢)0.765s151.00x
🐘 PostgresNitro1.263s (-46.3% 🟢)2.007s (-33.3% 🟢)0.744s151.02x
🐘 PostgresNext.js (Turbopack)1.361s2.007s0.646s151.10x
💻 LocalNext.js (Turbopack)1.611s2.005s0.394s151.30x
💻 LocalExpress1.713s (-42.0% 🟢)2.005s (-41.9% 🟢)0.292s151.38x
💻 LocalNitro1.739s (-44.7% 🟢)2.005s (-48.4% 🟢)0.267s151.40x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.542s (-12.6% 🟢)5.492s (-7.2% 🟢)1.950s61.00x
▲ VercelExpress3.915s (+8.2% 🔺)5.554s (+8.7% 🔺)1.639s61.11x
▲ VercelNext.js (Turbopack)3.939s (-44.5% 🟢)5.730s (-35.7% 🟢)1.791s61.11x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.383s (-60.3% 🟢)2.008s (-49.9% 🟢)0.625s151.00x
🐘 PostgresNitro1.417s (-59.3% 🟢)2.008s (-49.9% 🟢)0.592s151.02x
🐘 PostgresNext.js (Turbopack)1.672s2.152s0.480s141.21x
💻 LocalNext.js (Turbopack)3.777s4.438s0.661s72.73x
💻 LocalNitro4.388s (-47.4% 🟢)5.012s (-44.4% 🟢)0.624s63.17x
💻 LocalExpress4.926s (-40.9% 🟢)5.345s (-40.8% 🟢)0.419s63.56x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.090s (+20.0% 🔺)6.683s (+9.1% 🔺)1.593s51.00x
▲ VercelNitro5.393s (+53.0% 🔺)7.105s (+28.4% 🔺)1.711s51.06x
▲ VercelNext.js (Turbopack)5.726s (-35.8% 🟢)8.078s (-26.3% 🟢)2.352s41.13x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.178s (-6.3% 🟢)2.009s (~)0.831s151.00x
🐘 PostgresExpress1.183s (-5.9% 🟢)2.009s (~)0.826s151.00x
🐘 PostgresNext.js (Turbopack)1.226s2.008s0.781s151.04x
💻 LocalNext.js (Turbopack)1.296s2.005s0.709s151.10x
💻 LocalNitro1.490s (-20.2% 🟢)2.006s (-14.3% 🟢)0.516s151.26x
💻 LocalExpress1.560s (-17.6% 🟢)2.006s (-15.1% 🟢)0.446s151.32x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.807s (+8.8% 🔺)4.406s (+1.3%)1.598s71.00x
▲ VercelNext.js (Turbopack)3.199s (+9.1% 🔺)5.271s (+13.6% 🔺)2.072s61.14x
▲ VercelNitro4.085s (+66.1% 🔺)5.747s (+37.8% 🔺)1.662s61.46x

🔍 Observability: Express | Next.js (Turbopack) | Nitro

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.247s (-46.8% 🟢)2.009s (-33.3% 🟢)0.762s151.00x
🐘 PostgresNitro1.251s (-46.5% 🟢)2.011s (-33.2% 🟢)0.760s151.00x
🐘 PostgresNext.js (Turbopack)1.388s2.008s0.620s151.11x
💻 LocalNext.js (Turbopack)1.692s2.071s0.379s151.36x
💻 LocalNitro1.811s (-40.9% 🟢)2.151s (-44.7% 🟢)0.340s141.45x
💻 LocalExpress2.087s (-33.4% 🟢)2.591s (-31.1% 🟢)0.504s121.67x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.368s (+4.2%)4.827s (-4.9%)1.459s71.00x
▲ VercelExpress3.655s (+14.5% 🔺)5.117s (+6.8% 🔺)1.462s61.09x
▲ VercelNext.js (Turbopack)3.792s (+20.7% 🔺)5.880s (+30.0% 🔺)2.088s61.13x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.379s (-60.6% 🟢)2.009s (-49.9% 🟢)0.631s151.00x
🐘 PostgresNitro1.393s (-60.0% 🟢)2.009s (-49.9% 🟢)0.615s151.01x
🐘 PostgresNext.js (Turbopack)1.678s2.076s0.398s151.22x
💻 LocalNext.js (Turbopack)4.093s4.440s0.347s72.97x
💻 LocalNitro5.417s (-40.8% 🟢)5.848s (-41.7% 🟢)0.431s63.93x
💻 LocalExpress5.923s (-32.7% 🟢)6.215s (-33.0% 🟢)0.292s54.30x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.910s (-3.6%)6.414s (-5.9% 🟢)1.503s51.00x
▲ VercelExpress5.204s (-18.9% 🟢)7.221s (-11.7% 🟢)2.017s51.06x
▲ VercelNext.js (Turbopack)5.597s (-17.2% 🟢)7.635s (-10.6% 🟢)2.037s41.14x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.573s (-31.7% 🟢)1.024s (~)0.451s591.00x
🐘 PostgresNitro0.583s (-29.0% 🟢)1.024s (+1.8%)0.441s591.02x
💻 LocalNitro0.670s (-31.6% 🟢)1.096s (~)0.425s551.17x
💻 LocalExpress0.690s (-29.9% 🟢)1.096s (+1.8%)0.406s551.20x
💻 LocalNext.js (Turbopack)0.739s1.056s0.317s571.29x
🐘 PostgresNext.js (Turbopack)0.810s1.007s0.197s601.41x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.949s (-73.0% 🟢)7.255s (-69.8% 🟢)1.306s91.00x
▲ VercelNext.js (Turbopack)6.083s (-58.1% 🟢)7.967s (-50.5% 🟢)1.884s81.02x
▲ VercelExpress6.348s (-66.6% 🟢)8.017s (-62.4% 🟢)1.669s81.07x

🔍 Observability: Nitro | Next.js (Turbopack) | Express

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.335s (-32.4% 🟢)2.007s (-11.1% 🟢)0.672s451.00x
🐘 PostgresNitro1.379s (-28.4% 🟢)2.030s (-3.3%)0.651s451.03x
💻 LocalNitro1.452s (-52.2% 🟢)2.005s (-46.6% 🟢)0.554s451.09x
💻 LocalExpress1.506s (-50.1% 🟢)2.006s (-44.1% 🟢)0.500s451.13x
💻 LocalNext.js (Turbopack)1.738s2.050s0.313s441.30x
🐘 PostgresNext.js (Turbopack)1.886s2.052s0.167s441.41x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro14.124s (-64.2% 🟢)16.579s (-59.9% 🟢)2.455s61.00x
▲ VercelExpress14.353s (-58.4% 🟢)16.396s (-55.5% 🟢)2.043s61.02x
▲ VercelNext.js (Turbopack)14.586s (-70.7% 🟢)16.747s (-67.6% 🟢)2.161s61.03x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.610s (-36.4% 🟢)3.034s (-34.1% 🟢)0.424s401.00x
🐘 PostgresExpress2.630s (-34.1% 🟢)3.033s (-30.6% 🟢)0.403s401.01x
💻 LocalNitro3.210s (-65.5% 🟢)3.911s (-61.0% 🟢)0.701s311.23x
💻 LocalExpress3.257s (-64.6% 🟢)4.009s (-60.0% 🟢)0.751s301.25x
💻 LocalNext.js (Turbopack)3.760s4.180s0.420s291.44x
🐘 PostgresNext.js (Turbopack)3.793s4.044s0.250s301.45x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro28.002s (-71.1% 🟢)30.090s (-69.4% 🟢)2.089s41.00x
▲ VercelExpress30.707s (-76.4% 🟢)32.853s (-75.1% 🟢)2.146s41.10x
▲ VercelNext.js (Turbopack)33.075s (-69.1% 🟢)35.180s (-67.7% 🟢)2.104s41.18x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.240s (-15.2% 🟢)1.006s (~)0.767s601.00x
🐘 PostgresNitro0.243s (-14.3% 🟢)1.007s (~)0.764s601.01x
🐘 PostgresNext.js (Turbopack)0.264s1.007s0.742s601.10x
💻 LocalNitro0.430s (-28.9% 🟢)1.004s (-1.7%)0.574s601.80x
💻 LocalExpress0.440s (-21.5% 🟢)1.004s (~)0.564s601.84x
💻 LocalNext.js (Turbopack)0.535s1.004s0.469s602.23x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)2.258s (+11.6% 🔺)3.931s (+3.6%)1.674s161.00x
▲ VercelNitro2.317s (+39.5% 🔺)3.793s (+13.2% 🔺)1.476s171.03x
▲ VercelExpress2.867s (+46.7% 🔺)4.528s (+24.5% 🔺)1.661s141.27x

🔍 Observability: Next.js (Turbopack) | Nitro | Express

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.348s (-31.8% 🟢)1.007s (~)0.659s901.00x
🐘 PostgresNitro0.366s (-26.2% 🟢)1.006s (~)0.640s901.05x
🐘 PostgresNext.js (Turbopack)0.462s1.006s0.544s901.33x
💻 LocalNext.js (Turbopack)1.970s2.535s0.565s365.66x
💻 LocalNitro2.153s (-15.2% 🟢)2.686s (-10.7% 🟢)0.533s346.19x
💻 LocalExpress2.157s (-14.2% 🟢)2.736s (-9.1% 🟢)0.579s336.20x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.036s (+56.1% 🔺)6.840s (+41.9% 🔺)1.804s141.00x
▲ VercelExpress5.719s (+87.7% 🔺)6.943s (+44.4% 🔺)1.224s131.14x
▲ VercelNext.js (Turbopack)5.909s (+67.1% 🔺)7.860s (+51.3% 🔺)1.951s121.17x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.666s (-18.7% 🟢)1.006s (-1.1%)0.340s1201.00x
🐘 PostgresNitro0.667s (-15.6% 🟢)1.006s (~)0.339s1201.00x
🐘 PostgresNext.js (Turbopack)0.948s1.356s0.408s891.42x
💻 LocalNext.js (Turbopack)8.599s9.328s0.729s1312.91x
💻 LocalNitro9.341s (-16.5% 🟢)10.025s (-14.0% 🟢)0.684s1214.03x
💻 LocalExpress9.552s (-14.6% 🟢)10.029s (-16.0% 🟢)0.478s1214.34x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)16.273s (+57.6% 🔺)18.507s (+50.6% 🔺)2.234s71.00x
▲ VercelNitro17.364s (+124.8% 🔺)18.882s (+100.9% 🔺)1.518s81.07x
▲ VercelExpress17.612s (+137.4% 🔺)19.312s (+108.9% 🔺)1.700s71.08x

🔍 Observability: Next.js (Turbopack) | Nitro | Express

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.160s (+482.6% 🔺)2.005s (+99.6% 🔺)0.011s (-11.6% 🟢)2.018s (+98.2% 🔺)0.858s101.00x
💻 LocalNext.js (Turbopack)1.161s2.002s0.008s2.014s0.853s101.00x
🐘 PostgresExpress1.166s (+468.4% 🔺)1.996s (+99.9% 🔺)0.001s (-31.3% 🟢)2.010s (+98.7% 🔺)0.844s101.01x
🐘 PostgresNitro1.167s (+469.3% 🔺)1.999s (+100.0% 🔺)0.001s (-33.3% 🟢)2.012s (+99.0% 🔺)0.845s101.01x
💻 LocalNitro1.168s (+446.4% 🔺)2.005s (+99.6% 🔺)0.010s (-18.4% 🟢)2.018s (+98.0% 🔺)0.850s101.01x
🐘 PostgresNext.js (Turbopack)1.254s2.001s0.002s2.011s0.756s101.08x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.287s (-8.7% 🟢)3.123s (-23.6% 🟢)1.846s (+92.2% 🔺)5.490s (-1.8%)3.203s101.00x
▲ VercelNitro2.406s (-37.2% 🟢)3.481s (-34.0% 🟢)2.459s (+231.3% 🔺)6.467s (~)4.061s101.05x
▲ VercelNext.js (Turbopack)2.503s (-63.5% 🟢)3.970s (-54.1% 🟢)6.701s (+960.4% 🔺)11.246s (+14.9% 🔺)8.743s101.09x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.569s (+87.1% 🔺)2.012s (+98.9% 🔺)0.010s (+2.8%)2.024s (+81.4% 🔺)0.455s301.00x
💻 LocalNext.js (Turbopack)1.584s2.009s0.008s2.019s0.435s301.01x
💻 LocalExpress1.586s (+109.5% 🔺)2.010s (+95.4% 🔺)0.010s (+2.3%)2.022s (+94.5% 🔺)0.436s301.01x
🐘 PostgresExpress1.589s (+152.2% 🔺)2.000s (+98.7% 🔺)0.004s (+4.4%)2.025s (+98.0% 🔺)0.436s301.01x
🐘 PostgresNitro1.610s (+157.9% 🔺)2.005s (+99.2% 🔺)0.004s (-4.1%)2.026s (+98.1% 🔺)0.416s301.03x
🐘 PostgresNext.js (Turbopack)1.722s2.011s0.004s2.027s0.304s301.10x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.101s (-79.3% 🟢)7.626s (-75.2% 🟢)0.341s (+204.6% 🔺)8.371s (-73.7% 🟢)2.270s81.00x
▲ VercelExpress6.786s (+4.3%)8.192s (+2.3%)0.209s (-48.8% 🟢)8.763s (-0.8%)1.977s71.11x
▲ VercelNext.js (Turbopack)7.665s (-54.7% 🟢)9.196s (-49.6% 🟢)0.326s (+54.5% 🔺)10.168s (-46.3% 🟢)2.503s71.26x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.698s (-27.9% 🟢)1.031s (-17.4% 🟢)0.000s (+24.1% 🔺)1.048s (-16.6% 🟢)0.350s581.00x
🐘 PostgresExpress0.724s (-24.7% 🟢)1.050s (-17.8% 🟢)0.000s (+61.4% 🔺)1.062s (-18.7% 🟢)0.338s571.04x
🐘 PostgresNext.js (Turbopack)0.809s1.053s0.000s1.059s0.250s571.16x
💻 LocalNext.js (Turbopack)1.302s1.948s0.000s1.951s0.649s311.87x
💻 LocalNitro1.448s (+18.5% 🔺)2.015s (~)0.000s (~)2.017s (~)0.568s302.07x
💻 LocalExpress1.500s (+22.4% 🔺)2.016s (~)0.000s (-20.0% 🟢)2.018s (~)0.518s302.15x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.741s (~)5.040s (-1.2%)0.000s (-54.2% 🟢)5.454s (-1.4%)1.713s121.00x
▲ VercelNext.js (Turbopack)4.081s (-59.9% 🟢)5.397s (-53.1% 🟢)0.001s (+Infinity% 🔺)5.909s (-51.0% 🟢)1.828s111.09x
▲ VercelNitro4.159s (+36.4% 🔺)5.444s (+23.9% 🔺)0.000s (+136.4% 🔺)5.989s (+24.5% 🔺)1.830s111.11x

🔍 Observability: Express | Next.js (Turbopack) | Nitro

fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.314s (-26.7% 🟢)2.068s (-3.4%)0.000s (-3.4%)2.079s (-4.4%)0.765s291.00x
🐘 PostgresExpress1.403s (-20.8% 🟢)2.102s (-3.5%)0.000s (+Infinity% 🔺)2.113s (-3.9%)0.711s291.07x
🐘 PostgresNext.js (Turbopack)1.626s2.216s0.000s2.223s0.597s281.24x
💻 LocalNext.js (Turbopack)2.651s3.184s0.001s3.187s0.536s192.02x
💻 LocalExpress3.346s (-3.5%)3.907s (-3.1%)0.001s (-14.1% 🟢)3.910s (-3.1%)0.564s162.55x
💻 LocalNitro3.359s (-0.9%)4.031s (~)0.001s (+25.0% 🔺)4.034s (~)0.675s152.56x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.662s (+38.3% 🔺)7.236s (+34.6% 🔺)0.000s (-54.2% 🟢)7.657s (+32.2% 🔺)1.995s81.00x
▲ VercelNext.js (Turbopack)6.228s (+10.9% 🔺)7.643s (+9.5% 🔺)0.000s (+100.0% 🔺)8.158s (+8.2% 🔺)1.930s81.10x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNitro9/21
🐘 PostgresExpress14/21
▲ VercelNitro10/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres14/21
Next.js (Turbopack)🐘 Postgres11/21
Nitro🐘 Postgres15/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Redis + BullMQ: Community world (local development)
  • 🌐 Cloudflare: Community world (local development)
  • 🌐 MySQL: Community world (local development)
  • 🌐 Azure: Community world (local development)
  • 🌐 NATS JetStream: Community world (local development)
  • 🌐 Upstash: Community world (local development)

📋 View full workflow run

@mitul-s
mitul-s enabled auto-merge (squash) May 28, 2026 20:40
@mitul-s
mitul-s merged commit e8e5292 into mainMay 28, 2026
277 of 301 checks passed
@mitul-s
mitul-s deleted the ms/geist-button-states branch May 28, 2026 23:24
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for e8e5292 (AI decision).

The primary file changed (packages/web-shared/src/components/ui/button.tsx) does not exist on stable — the shared Button component is main-only. The accompanying --ds-focus-color additions in styles.css exist solely to support that Button (and an icon-button also absent on stable), so backporting only the CSS variable wouldn't fix any user-visible behavior on stable.

To override, re-run the Backport to stable workflow manually via workflow_dispatch and paste this commit SHA into the ref input:

e8e52925ad9603b6d613a5bb94d7c17e4a7f26be

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@mitul-s@karthikscale3