Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
176 commits
Select commit Hold shift + click to select a range
fb0667f
docs: add theme studio master plan
mikenewbon Jul 4, 2026
ccaa4ca
feat(docs): add Theme Studio v0 page
mikenewbon Jul 4, 2026
8c26772
feat(docs): add theme engine and studio presets
mikenewbon Jul 4, 2026
9301f24
feat(docs): add OKLCH palette curve editor to theme studio
mikenewbon Jul 4, 2026
4ba2c59
fix(docs): make palette editor opt-in and escapable
mikenewbon Jul 4, 2026
f2f8f50
fix(docs): match generated ramps to tailwind's color scale shape
mikenewbon Jul 4, 2026
19bdb2d
feat(docs): channel-curve palette editor with tailwind reverse-fitting
mikenewbon Jul 4, 2026
e3837d5
feat(docs): stream theme updates live while dragging curves
mikenewbon Jul 4, 2026
6135c97
feat(docs): neutral-50 light background baseline + tighter drag trans…
mikenewbon Jul 4, 2026
f14e176
fix(docs): keep dark mode background intact under the neutral-50 base…
mikenewbon Jul 4, 2026
6853b96
feat(docs): shadow/border style controls and neo-brutalist preset
mikenewbon Jul 5, 2026
89c27a2
fix(docs): make bold/hard borders win over variant ring colors
mikenewbon Jul 5, 2026
0a1a740
fix(docs): correct bold-border and hard-shadow semantics
mikenewbon Jul 5, 2026
41babb0
feat(docs): holistic style system, masonry bento, reference brutalist…
mikenewbon Jul 5, 2026
a3076dd
feat(docs): four new presets and a compact settings panel
mikenewbon Jul 5, 2026
c175d14
feat(docs): per-mode neutral shade picker for shadow color
mikenewbon Jul 5, 2026
a0267ff
feat(docs): per-mode shade sliders for borders and background, palett…
mikenewbon Jul 5, 2026
accd314
feat(docs): collapsible layout + presets hydrate the settings panel
mikenewbon Jul 5, 2026
c768509
feat(docs): semantic token shade sections and candy pastel offsets
mikenewbon Jul 5, 2026
8deeb3a
docs: mike takes the content tile
mikenewbon Jul 5, 2026
9ef31ed
refactor(docs): adversarial review fixes across the theme studio
mikenewbon Jul 5, 2026
f9a2eff
feat(docs): default variants, shadow geometry/opacity, accordion groups
mikenewbon Jul 5, 2026
e9c1a40
feat(docs): sidebar polish — mode tabs, single-open groups, ghost sec…
mikenewbon Jul 5, 2026
289b76d
fix(docs): flatten analytics payload to primitives for typecheck
mikenewbon Jul 5, 2026
43e212a
feat(docs): custom palettes, shuffle depth, offset effect slider
mikenewbon Jul 5, 2026
d7841ce
feat(docs): primary shade sliders, primary-ramp shadow/border shades,…
mikenewbon Jul 5, 2026
23f1fae
feat(docs): Cabin and Harbor presets on the studio's own palettes
mikenewbon Jul 5, 2026
2226402
feat(docs): oklch-native palette pipeline — generate, store and expor…
mikenewbon Jul 5, 2026
46f0307
feat(docs): flat ghost shadows, full token slider coverage, font-size…
mikenewbon Jul 5, 2026
573adca
feat(docs): Orchard preset — orange on a sculpted grove neutral, exer…
mikenewbon Jul 5, 2026
4cf1629
feat(docs): semantic palette pickers, Background rename, card-wrapped…
mikenewbon Jul 5, 2026
ae8cccb
feat(docs): inline palette-edit icons and custom curve editing for se…
mikenewbon Jul 5, 2026
1e55e36
fix(docs): update palette and color-parse specs to the oklch-native p…
mikenewbon Jul 5, 2026
a193edd
feat(docs): legend chips on every sidebar section, edit buttons group…
mikenewbon Jul 5, 2026
0096bd1
feat(docs): per-group variant options matching each component's real …
mikenewbon Jul 5, 2026
79975db
feat(docs): preview view tabs with template-faithful Dashboard and Ch…
mikenewbon Jul 5, 2026
8109504
feat(docs): theme importer — parse exported CSS/config back into edit…
mikenewbon Jul 5, 2026
35aeba8
fix(docs): border recoloring no longer repaints semantic outline/subt…
mikenewbon Jul 5, 2026
ae5864b
feat(docs): SaaS and Landing preview views from the real templates
mikenewbon Jul 5, 2026
2f72b57
fix(docs): audit fixes — input-family and overlay treatment coverage,…
mikenewbon Jul 5, 2026
0fe2768
feat(docs): section the palette pickers into Colors and Neutrals
mikenewbon Jul 5, 2026
7abd4da
fix(docs): resolve the mode tab selection after mount — SSR can't kno…
mikenewbon Jul 5, 2026
25bf100
feat(docs): resculpted Harbor preset and refreshed sand ramp
mikenewbon Jul 5, 2026
741f565
feat(docs): rework border controls — Default/None/Custom with a width…
mikenewbon Jul 5, 2026
fbf36f9
feat(docs): frame toggle under custom borders, matched option order, …
mikenewbon Jul 5, 2026
bd6fc63
feat(docs): border width reaches chrome edges, separators and divider…
mikenewbon Jul 5, 2026
971ed0e
feat(docs): studio chrome follows the border width too
mikenewbon Jul 5, 2026
dc8e243
feat(docs): border width sweep — full component coverage live, tailwi…
mikenewbon Jul 6, 2026
002fdf0
feat(docs): --studio-border-width variable scales all hand-rolled pre…
mikenewbon Jul 6, 2026
f853241
feat(docs): neo-brutalist rebuilt on the carbon neutral, art deco ret…
mikenewbon Jul 6, 2026
4e7beae
fix(docs): curve editor — fixed 1:1 axes, window-clamped drags, hue n…
mikenewbon Jul 6, 2026
4ce960d
fix(docs): dashboard panel children keep their height — flex was crus…
mikenewbon Jul 6, 2026
ddedd68
feat(docs): enhance ThemeStudioControls with preset swatches and new …
mikenewbon Jul 6, 2026
ccfdb13
fix(docs): dead semantic help link fataled the prerender; heap headro…
mikenewbon Jul 6, 2026
5a68c91
refactor(docs): drop skin classes and ui injections from studio contr…
mikenewbon Jul 6, 2026
8642837
feat(docs): A11y view — live WCAG contrast matrix; contrast badge ret…
mikenewbon Jul 6, 2026
58d9ff3
refactor(docs): sidebar sections on UCollapsible, clearer shade label…
mikenewbon Jul 6, 2026
1c42cb0
feat(docs): Theme Studio becomes a fullscreen modal; simplified picke…
mikenewbon Jul 6, 2026
2389a71
fix(docs): update ThemeStudioModal color to primary for improved visi…
mikenewbon Jul 6, 2026
e288b6b
Merge branch 'v4' into docs/theme-studio-plan
mikenewbon Jul 6, 2026
ece3b31
feat(docs): studio toolbar spans the full modal width above the sidebar
mikenewbon Jul 6, 2026
918243b
feat(docs): settings panel on USidebar — offcanvas collapse, Import/E…
mikenewbon Jul 6, 2026
a0dc9f4
feat(docs): floating sidebar layout restored full-height; studio chro…
mikenewbon Jul 6, 2026
8081226
fix(docs): preview card owns its start margin so it keeps its inset w…
mikenewbon Jul 6, 2026
1a316fe
feat(docs): inner shadow axis — inset treatment across the same surfa…
mikenewbon Jul 6, 2026
633fd8a
feat(docs): inner shadow gets its own color select and shade sliders,…
mikenewbon Jul 6, 2026
bc11eca
fix(docs): restore the sidebar pe-0 / card-owned margin fix that regr…
mikenewbon Jul 6, 2026
2add887
feat(docs): Orchard resculpted — bottom-lit inner shadow on grove 500…
mikenewbon Jul 6, 2026
a072bda
feat(docs): logo and mode buttons move into the sidebar header, tinte…
mikenewbon Jul 6, 2026
f5aa2ef
fix(docs): neo defaults from paste; sidebar dividers, dashboard sideb…
mikenewbon Jul 6, 2026
3d481a9
feat(docs): default border/ring widths compile to the studio width va…
mikenewbon Jul 6, 2026
cbb3580
refactor(docs): unify sidebar and preview card shadow treatments; str…
mikenewbon Jul 6, 2026
a68fd61
refactor(docs): one ThemePickerColorMenu — the studio's sectioned swa…
mikenewbon Jul 6, 2026
a38cba0
feat(docs): Landing and SaaS previews faithful to the real templates …
mikenewbon Jul 6, 2026
1beaa02
fix(docs): landing polish — glass badge per template, pinned solid CT…
mikenewbon Jul 6, 2026
f54a676
feat(docs): ?studio query param — the open studio survives reloads an…
mikenewbon Jul 6, 2026
dc2cb7d
fix(docs): badge blur freed from the entrance animation's backdrop ro…
mikenewbon Jul 6, 2026
5b7c032
feat(docs): curve editor endpoints wear the ramp's 50 and 950 colors
mikenewbon Jul 6, 2026
ad2f3d5
fix(docs): sidebar slot rounding scoped to desktop — the mobile sheet…
mikenewbon Jul 6, 2026
6ed248b
refactor(docs): retire the border-width class machinery — widths ride…
mikenewbon Jul 6, 2026
fab9d64
feat(docs): undo/redo over theme documents — footer buttons plus cmd/…
mikenewbon Jul 6, 2026
3563264
refactor(docs): settings panel regrouped — Scale and Components join …
mikenewbon Jul 6, 2026
b07d353
Merge branch 'v4' into docs/theme-studio-plan
mikenewbon Jul 6, 2026
b4fbd3b
fix(docs): type-safe undo snapshot getter — undefined before mount, n…
mikenewbon Jul 6, 2026
403045b
feat(docs): section legends are real buttons — label plus trailing ch…
mikenewbon Jul 6, 2026
fe8cda2
refactor(docs): adversarial review pass — shared components, hot-path…
mikenewbon Jul 6, 2026
3b4848e
refactor(docs): review tail — bento chat on real components, determin…
mikenewbon Jul 6, 2026
6aed6a5
feat(docs): Forest preset — sculpted evergreen and forest neutrals, s…
mikenewbon Jul 6, 2026
ab7c564
feat(docs): Anthropic preset on the real palette — brand clay anchore…
mikenewbon Jul 6, 2026
0f74982
fix(docs): update dark theme background color from neutral-950 to neu…
mikenewbon Jul 6, 2026
e300500
feat(docs): enhance Theme Studio components with size adjustments
mikenewbon Jul 7, 2026
2a4c480
feat(docs): settings panel moves right — close in its header, view dr…
mikenewbon Jul 7, 2026
d199582
feat(docs): sidebar refinement pass — inset panel with tuned padding,…
mikenewbon Jul 7, 2026
f9c5a99
feat(docs): Theme Studio becomes a left-edge drawer with a settings s…
mikenewbon Jul 7, 2026
2b34207
feat(docs): studio drawer from the right — settings strip snaps in, c…
mikenewbon Jul 7, 2026
2123304
feat(docs): Theme Studio returns as the /theme page
mikenewbon Jul 7, 2026
8c0dc2f
feat(docs): studio toolbar with setting popovers, split component def…
mikenewbon Jul 7, 2026
4658982
feat(docs): add UColorModeButton to theme page for improved color mod…
mikenewbon Jul 7, 2026
77dbc39
feat(docs): Marshmallow resculpted — pink-mauve custom neutral, pink-…
mikenewbon Jul 7, 2026
aaa0d5c
feat(docs): Anthropic restyle — cream page under ivory surfaces, blur…
mikenewbon Jul 7, 2026
b31b7b9
feat(docs): remove 'Forest' preset and update 'Marshmallow' with new …
mikenewbon Jul 7, 2026
853ab96
fix(docs): studio fullscreen state can't hide the chrome on other pages
mikenewbon Jul 7, 2026
ecc88fc
feat(docs): studio controls move into the site header on /theme
mikenewbon Jul 9, 2026
7b453d0
feat(docs): bottom toolbar, fullscreen floating bar, header view tabs
mikenewbon Jul 9, 2026
2045a34
refactor(docs): header view tabs move into the navigation menu's list…
mikenewbon Jul 9, 2026
7d6db7c
feat(docs): studio menu nests resources one level down
mikenewbon Jul 9, 2026
de1d29d
feat(docs): studio grid renders the landing playground masonry
mikenewbon Jul 9, 2026
25d257e
fix(docs): fullscreen toolbar gets container max-width and a taller r…
mikenewbon Jul 9, 2026
74fd8d0
fix(docs): adjust studio menu popover width for improved layout
mikenewbon Jul 9, 2026
7e5359b
feat(docs): palette curve editors get a live 2D color field background
mikenewbon Jul 9, 2026
c57e93d
feat(docs): smooth the curve editor color field, drop the grid lines
mikenewbon Jul 9, 2026
6ed2a48
fix(docs): curve editor field columns align fence-post with the plot
mikenewbon Jul 9, 2026
decb68e
fix(docs): playground prompt tile no longer steals focus on load
mikenewbon Jul 9, 2026
7bbe30f
feat(docs): slim theme popover on the header swatch button
mikenewbon Jul 9, 2026
37cdb2e
fix(docs): preset menu names the applied preset across reloads, says …
mikenewbon Jul 9, 2026
d7acb94
feat(docs): shades fold into the Colors popover
mikenewbon Jul 9, 2026
af1bcde
fix(docs): CodeRabbit review pass
mikenewbon Jul 9, 2026
c83fd13
fix(docs): adversarial review pass — history, fullscreen, palette and…
mikenewbon Jul 9, 2026
cc54613
feat(docs): neutral-as-primary, leaner color controls and layered pal…
mikenewbon Jul 10, 2026
f41c925
feat(docs): collapsible studio sections, reworked palette modifiers a…
mikenewbon Jul 10, 2026
0a4e8a8
feat(docs): strict color roles for v4, active fold triggers
mikenewbon Jul 10, 2026
55f32b7
fix(docs): adjust ThemeStudioColorMenu and ThemeStudioSection for imp…
mikenewbon Jul 10, 2026
daef31f
fix(docs): update button variant in ThemeStudioPaletteEditor for impr…
mikenewbon Jul 10, 2026
f9255a1
refactor(docs): consolidate the studio to fewer, stockier components
mikenewbon Jul 10, 2026
05af01c
refactor(docs): enhance theme studio with palette effects and UI impr…
mikenewbon Jul 10, 2026
c762b41
refactor(docs): enhance shadow handling and UI consistency in Theme S…
mikenewbon Jul 10, 2026
cf162ac
feat(docs): typography system — weights, heading treatment and richer…
mikenewbon Jul 10, 2026
eb9aa2a
fix(docs): adjust padding in theme.vue for improved layout consistency
mikenewbon Jul 10, 2026
8f7f28e
feat(docs): enhance ThemeStudioPaletteEditor with swatch tooltips and…
mikenewbon Jul 10, 2026
da6cb0e
Merge branch 'v4' into docs/theme-studio-plan
mikenewbon Jul 10, 2026
274d1da
feat(docs): structured borders, richer icon sets and a Bootstrap 5 pr…
mikenewbon Jul 11, 2026
aa34b53
fix(docs): adversarial review pass over the full theme studio PR
mikenewbon Jul 11, 2026
aed4e55
fix(docs): color-mode shortcut lands on ⌘⇧D; CI typecheck fix
mikenewbon Jul 11, 2026
7684316
fix(docs): color-mode shortcut settles on ⌃⇧D
mikenewbon Jul 11, 2026
d8a729b
fix(docs): custom shadow seed visible at 20%, inherit clears the knobs
mikenewbon Jul 11, 2026
c89a698
feat(docs): playground grid rides a virtualized UScrollArea masonry
mikenewbon Jul 11, 2026
a033494
feat(docs): listbox-powered theme pickers
mikenewbon Jul 11, 2026
887d36a
fix(docs): refine theme studio components for improved usability
mikenewbon Jul 11, 2026
87a425d
feat(docs): enhance theme studio components with default values and s…
mikenewbon Jul 11, 2026
da65ac0
feat(docs): enhance Playground and Theme Studio components with new f…
mikenewbon Jul 11, 2026
ff6dba7
feat(docs): update PlaygroundContributors.vue to enhance contributor …
mikenewbon Jul 11, 2026
2bd89a0
feat(docs): enhance theme studio components with reset functionality
mikenewbon Jul 11, 2026
a72ef89
feat(docs): add reset functionality to theme.vue for improved theme m…
mikenewbon Jul 11, 2026
4617d2c
feat(docs): implement enhanced reset functionality across theme studi…
mikenewbon Jul 12, 2026
9a238d0
refactor(docs): engine dedupe, dead-code sweep and white/black shade …
mikenewbon Jul 12, 2026
b7dc498
refactor(docs): shared pickers and a three-way split for theme studio…
mikenewbon Jul 12, 2026
ce574fc
feat(docs): portfolio, changelog, docs and editor previews with a lis…
mikenewbon Jul 12, 2026
fda15c6
feat(docs): update ThemeStudioSection and ThemeStudioViewPortfolio wi…
mikenewbon Jul 12, 2026
876e371
fix(docs): refine header and theme studio components for improved cla…
mikenewbon Jul 12, 2026
69d0ea2
feat(docs): introduce ThemeStudioFontPicker and Google Fonts integration
mikenewbon Jul 14, 2026
0c492db
feat(docs): enhance header and theme studio views with image previews
mikenewbon Jul 14, 2026
35f42c6
feat(docs): refactor theme studio header and introduce new components
mikenewbon Jul 15, 2026
ba18c5b
feat(docs): enhance theme management with style token integration
mikenewbon Jul 15, 2026
328f23f
Merge branch 'v4' into docs/theme-studio-plan
mikenewbon Jul 15, 2026
333b360
Merge branch 'v4' into docs/theme-studio-plan
benjamincanac Jul 16, 2026
6adf871
refactor(docs): simplify header and theme management logic
mikenewbon Jul 16, 2026
25c2808
feat(theme-engine): enhance CSS generation for light and dark themes
mikenewbon Jul 17, 2026
405bd59
fix(theme-studio): refine slider step behavior and tooltip functionality
mikenewbon Jul 20, 2026
6127af1
fix(theme-studio): improve slider value clamping and keyboard interac…
mikenewbon Jul 20, 2026
d7968cd
feat(docs/theme): pack-aware icons across the studio, plus fixes
mikenewbon Jul 20, 2026
02a2c02
feat(theme-studio): add fine shade stops (150–850) to palette editor
mikenewbon Jul 20, 2026
04342fb
refactor(theme-studio): update stop terminology and enhance layout
mikenewbon Jul 20, 2026
0d2bce3
refactor(theme-studio): optimize swatch handling and performance
mikenewbon Jul 21, 2026
d7c359e
feat(theme-studio): pinned brand colours, curve accuracy, drag perfor…
mikenewbon Jul 25, 2026
ed71bb6
fix(theme-studio): persist multi-pins and keep endpoints on the curve
mikenewbon Jul 25, 2026
f88a9c1
fix(theme-studio): stop stock ramps crashing the palette editor
mikenewbon Jul 25, 2026
217d9e4
refactor(theme-studio): improve swatch detail handling and animation
mikenewbon Jul 25, 2026
91eb659
feat(theme-studio): 25 and 10 shade intervals in the palette editor
mikenewbon Jul 25, 2026
80ef29d
Merge branch 'v4' into docs/theme-studio-plan
mikenewbon Jul 25, 2026
b98649f
fix(theme-studio): specs follow the stopStep API
mikenewbon Jul 25, 2026
2a63b56
fix(theme-studio): apply shadow shade slider in custom mode; trim com…
mikenewbon Jul 25, 2026
1512426
feat(theme-studio): add button and badge variant color specifications
mikenewbon Jul 25, 2026
204e2a8
refactor(theme-studio): collapse shadows into one Custom config
mikenewbon Jul 25, 2026
5c0ff55
feat(theme-studio): press-effect option, themed tabs, reset and stale…
mikenewbon Jul 27, 2026
addd3cb
fix(theme-studio): parse the CSS none keyword in oklch colors
mikenewbon Jul 27, 2026
3a16fb8
Merge branch 'v4' into docs/theme-studio-plan
mikenewbon Jul 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions docs/app/app.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<script setup lang="ts">
const route = useRoute()

/** The theme studio's UI-hiding fullscreen also hides the site chrome. */
const studioFullscreen = useState('theme-studio-fullscreen', () => false)
const appConfig = useAppConfig()
const { style, link, color } = useTheme()

Expand Down Expand Up @@ -40,7 +43,7 @@ provide('navigation', rootNavigation)

<div class="flex">
<div class="flex-1 min-w-0" :class="[route.path.startsWith('/docs/') && 'root']">
<template v-if="!route.path.startsWith('/examples')">
<template v-if="!route.path.startsWith('/examples') && !(route.path === '/theme' && studioFullscreen)">
<!-- <Banner /> -->

<Header />
Expand All @@ -50,9 +53,10 @@ provide('navigation', rootNavigation)
<NuxtPage />
</NuxtLayout>

<template v-if="!route.path.startsWith('/examples')">
<template v-if="!route.path.startsWith('/examples') && route.path !== '/theme'">
<Footer />
</template>
<!-- footer stays off /theme: the studio is a fixed-height app page -->
</div>

<template v-if="!route.path.startsWith('/examples')">
Expand Down
100 changes: 100 additions & 0 deletions docs/app/assets/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,25 @@
@theme static {
--container-8xl: 90rem;

/* Every default-width border/ring/divide utility in the app compiles to
this var — the studio's width slider repaints the whole site through
one custom property instead of per-component class fragments. Exports
do the same thing statically (@theme --default-border-width). */
--default-border-width: var(--studio-border-width, 1px);
--default-ring-width: var(--studio-border-width, 1px);

/* Shadow ramp stays Tailwind-native by default — Inherit leaves the library
shadows completely untouched. Only under :root.shadow-custom does the
config-driven scale (--ui-shadow-c-*, defined below) take over each slot.
A var() fallback IS the native value, so the override is opt-in per size. */
--shadow-2xs: var(--ui-shadow-c-2xs, 0 1px rgb(0 0 0 / 0.05));
--shadow-xs: var(--ui-shadow-c-xs, 0 1px 2px 0 rgb(0 0 0 / 0.05));
--shadow-sm: var(--ui-shadow-c-sm, 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1));
--shadow-md: var(--ui-shadow-c-md, 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1));
--shadow-lg: var(--ui-shadow-c-lg, 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1));
--shadow-xl: var(--ui-shadow-c-xl, 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1));
--shadow-2xl: var(--ui-shadow-c-2xl, 0 25px 50px -12px rgb(0 0 0 / 0.25));

--font-sans: 'Public Sans', sans-serif;

--color-green-50: #EFFDF5;
Expand Down Expand Up @@ -54,3 +73,84 @@ html.dark .shiki span {
-webkit-mask-image: var(--shiki-icon-url);
mask-image: var(--shiki-icon-url);
}

/* Studio baselines. :where() keeps them at zero specificity: being
unlayered they still beat the library's @layer theme defaults, but any
user override from the theme studio (plain .light/.dark rules injected
via useHead) wins regardless of load order. */
:where(:root:not(.dark), .light) {
/* hard-shadow treatment: a shadow, not a glow */
--ui-shadow-color: var(--ui-color-neutral-950);
}

/* The one shadow config the whole scale derives from: geometry (offset/blur/
spread), colour and opacity. The studio's sliders / presets override these —
a Soft preset keeps the blur, a Hard preset drops it to 0 and leans on offset.
The resting geometry is the crisp fallback presets rely on when they don't
set their own; the --shadow-* factors scale it per size. */
:where(:root) {
--ui-shadow-offset-x: 3px;
--ui-shadow-offset-y: 3px;
--ui-shadow-blur: 0px;
--ui-shadow-spread: 0px;
--ui-shadow-final-hard: color-mix(in oklab, var(--ui-shadow-color) var(--ui-shadow-opacity, 100%), transparent);
/* The generalised ramp's alpha IS the opacity slider — uniform across sizes so
the slider reads true (100% is solid everywhere, for a neobrutalist theme;
low is subtle). Size is expressed through geometry (--ui-shadow-s-*), not by
fading small shadows, so a bold config stays bold on a badge as on a modal. */
--ui-shadow-alpha: var(--ui-shadow-opacity, 25%);
/* Per-size geometry scale (lg is the config's 1× reference); the offset/blur/
spread of the one config ride these so a tooltip's shadow is small and a
modal's large, from a single set of sliders. */
--ui-shadow-s-2xs: 0.15;
--ui-shadow-s-xs: 0.25;
--ui-shadow-s-sm: 0.4;
--ui-shadow-s-md: 0.65;
--ui-shadow-s-lg: 1;
--ui-shadow-s-xl: 1.5;
--ui-shadow-s-2xl: 2.2;
/* Composed press-effect shadows (button rests at 1×, hover sinks to ½×),
used as shadow-(--ui-shadow-hard*) so tailwind-merge can dedupe them
against stock shadow-sm/lg classes (it can't parse a full
shadow-[calc(...)_...] arbitrary value). */
--ui-shadow-hard: var(--ui-shadow-offset-x) var(--ui-shadow-offset-y) var(--ui-shadow-blur) var(--ui-shadow-spread) var(--ui-shadow-final-hard);
--ui-shadow-hard-half: calc(var(--ui-shadow-offset-x) / 2) calc(var(--ui-shadow-offset-y) / 2) var(--ui-shadow-blur) var(--ui-shadow-spread) var(--ui-shadow-final-hard);
/* Inner-shadow geometry defaults; shares --ui-shadow-color with the drop
treatment, with its own gentler opacity fallback. */
--ui-inner-shadow-offset-x: 0px;
--ui-inner-shadow-offset-y: 2px;
--ui-inner-shadow-blur: 4px;
--ui-inner-shadow-spread: 0px;
--ui-shadow-final-inner: color-mix(in oklab, var(--ui-inner-shadow-color, var(--ui-shadow-color)) var(--ui-inner-shadow-opacity, 15%), transparent);
--ui-inner-shadow: var(--ui-inner-shadow-offset-x) var(--ui-inner-shadow-offset-y) var(--ui-inner-shadow-blur) var(--ui-inner-shadow-spread) var(--ui-shadow-final-inner);
}

/* Custom shadow: one config — geometry (--ui-shadow-*), opacity (--ui-shadow-a-*)
and colour (--ui-shadow-color) — scaled per size (--ui-shadow-s-*) into the
ramp's override slots. Single-layer; blur 0 stays crisp, offset carries the
size ramp. Active only when the studio flags .shadow-custom on the root. */
:root:where(.shadow-custom) {
--ui-shadow-c-2xs: calc(var(--ui-shadow-offset-x) * var(--ui-shadow-s-2xs)) calc(var(--ui-shadow-offset-y) * var(--ui-shadow-s-2xs)) calc(var(--ui-shadow-blur) * var(--ui-shadow-s-2xs)) calc(var(--ui-shadow-spread) * var(--ui-shadow-s-2xs)) color-mix(in oklab, var(--ui-shadow-color) var(--ui-shadow-alpha), transparent);
--ui-shadow-c-xs: calc(var(--ui-shadow-offset-x) * var(--ui-shadow-s-xs)) calc(var(--ui-shadow-offset-y) * var(--ui-shadow-s-xs)) calc(var(--ui-shadow-blur) * var(--ui-shadow-s-xs)) calc(var(--ui-shadow-spread) * var(--ui-shadow-s-xs)) color-mix(in oklab, var(--ui-shadow-color) var(--ui-shadow-alpha), transparent);
--ui-shadow-c-sm: calc(var(--ui-shadow-offset-x) * var(--ui-shadow-s-sm)) calc(var(--ui-shadow-offset-y) * var(--ui-shadow-s-sm)) calc(var(--ui-shadow-blur) * var(--ui-shadow-s-sm)) calc(var(--ui-shadow-spread) * var(--ui-shadow-s-sm)) color-mix(in oklab, var(--ui-shadow-color) var(--ui-shadow-alpha), transparent);
--ui-shadow-c-md: calc(var(--ui-shadow-offset-x) * var(--ui-shadow-s-md)) calc(var(--ui-shadow-offset-y) * var(--ui-shadow-s-md)) calc(var(--ui-shadow-blur) * var(--ui-shadow-s-md)) calc(var(--ui-shadow-spread) * var(--ui-shadow-s-md)) color-mix(in oklab, var(--ui-shadow-color) var(--ui-shadow-alpha), transparent);
--ui-shadow-c-lg: calc(var(--ui-shadow-offset-x) * var(--ui-shadow-s-lg)) calc(var(--ui-shadow-offset-y) * var(--ui-shadow-s-lg)) calc(var(--ui-shadow-blur) * var(--ui-shadow-s-lg)) calc(var(--ui-shadow-spread) * var(--ui-shadow-s-lg)) color-mix(in oklab, var(--ui-shadow-color) var(--ui-shadow-alpha), transparent);
--ui-shadow-c-xl: calc(var(--ui-shadow-offset-x) * var(--ui-shadow-s-xl)) calc(var(--ui-shadow-offset-y) * var(--ui-shadow-s-xl)) calc(var(--ui-shadow-blur) * var(--ui-shadow-s-xl)) calc(var(--ui-shadow-spread) * var(--ui-shadow-s-xl)) color-mix(in oklab, var(--ui-shadow-color) var(--ui-shadow-alpha), transparent);
--ui-shadow-c-2xl: calc(var(--ui-shadow-offset-x) * var(--ui-shadow-s-2xl)) calc(var(--ui-shadow-offset-y) * var(--ui-shadow-s-2xl)) calc(var(--ui-shadow-blur) * var(--ui-shadow-s-2xl)) calc(var(--ui-shadow-spread) * var(--ui-shadow-s-2xl)) color-mix(in oklab, var(--ui-shadow-color) var(--ui-shadow-alpha), transparent);
}

/* None: strip every default shadow-* to nothing so raw content (screenshots)
goes flat like the components do under the None treatment. */
:root:where(.shadow-none) {
--ui-shadow-c-2xs: 0 0 transparent;
--ui-shadow-c-xs: 0 0 transparent;
--ui-shadow-c-sm: 0 0 transparent;
--ui-shadow-c-md: 0 0 transparent;
--ui-shadow-c-lg: 0 0 transparent;
--ui-shadow-c-xl: 0 0 transparent;
--ui-shadow-c-2xl: 0 0 transparent;
}

:where(.dark) {
--ui-shadow-color: black;
}
2 changes: 1 addition & 1 deletion docs/app/components/VersionMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const items = computed(() => {
variant="subtle"
:trailing-icon="appConfig.ui.icons.chevronDown"
size="xs"
class="-mb-[6px] font-semibold rounded-full truncate"
class="-mb-[6px] font-semibold rounded-xl truncate"
:class="[open && 'bg-primary/15']"
:ui="{
trailingIcon: ['transition-transform duration-200', open ? 'rotate-180' : undefined].filter(Boolean).join(' ')
Expand Down
2 changes: 1 addition & 1 deletion docs/app/components/chat/Chat.vue
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ defineShortcuts({
icon="i-lucide-rotate-ccw"
color="neutral"
variant="ghost"
@click="resetTheme"
@click="resetTheme()"
/>
</UTooltip>

Expand Down
17 changes: 13 additions & 4 deletions docs/app/components/header/Header.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,21 @@ const route = useRoute()
const { desktopLinks } = useHeader()
const { open } = useChat()
const { track } = useAnalytics()
// The Ask-AI button skins to the applied icon pack, like the rest of the
// studio chrome (the theme applies site-wide, so this stays consistent).
const studioIcons = useStudioIcons()

function toggleChat() {
if (!open.value) {
track('AI Chat Opened', { source: 'header' })
}
open.value = !open.value
}

// On /theme the header center becomes the studio's view switcher.
const isStudio = computed(() => route.path === '/theme')

const viewListOpen = ref(false)
</script>

<!-- eslint-disable vue/no-template-shadow -->
Expand All @@ -28,7 +36,8 @@ function toggleChat() {
<VersionMenu />
</template>

<UNavigationMenu :items="desktopLinks" variant="link" content-orientation="vertical" />
<ThemeStudioViewSwitcher v-if="isStudio" v-model:open="viewListOpen" />
<UNavigationMenu v-else :items="desktopLinks" variant="link" content-orientation="vertical" />

<template #right>
<UTooltip text="Search" :kbds="['meta', 'K']" ignore-non-keyboard-focus>
Expand All @@ -39,21 +48,21 @@ function toggleChat() {
<UButton
color="neutral"
variant="ghost"
icon="i-lucide-bot-message-square"
:icon="studioIcons.assistant"
aria-label="Ask AI for help"
@click="toggleChat"
/>
</UTooltip>

<ThemePicker />
<ThemeStudioPicker />

<UTooltip text="Open on GitHub" class="hidden lg:flex">
<UButton
color="neutral"
variant="ghost"
to="https://github.com/nuxt/ui"
target="_blank"
icon="i-simple-icons-github"
:icon="studioIcons.github"
aria-label="GitHub"
/>
</UTooltip>
Expand Down
96 changes: 96 additions & 0 deletions docs/app/components/playground/Playground.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<script setup lang="ts">
import {
PlaygroundInput,
PlaygroundCommandPalette,
PlaygroundCalendar,
PlaygroundButtons,
PlaygroundNotifications,
PlaygroundTransactions,
PlaygroundTransferFunds,
PlaygroundInviteTeam,
PlaygroundAnalytics,
PlaygroundPinInput,
PlaygroundBadges,
PlaygroundDashboard,
PlaygroundReportBug,
PlaygroundGoal,
PlaygroundSavingsTargets,
PlaygroundTypography,
PlaygroundShortcuts,
PlaygroundNavigation,
PlaygroundColorPicker,
PlaygroundEmpty,
PlaygroundAnnouncement,
PlaygroundTabs,
PlaygroundContributors,
PlaygroundAuthForm,
PlaygroundPrompt
} from '#components'

// Live, interactive component showcase — the Theme Studio's grid view, laid
// out as a virtualized masonry (UScrollArea lanes) of content-sized cards à
// la shadcn/HeroUI. Each tile is a self-contained, product-like block
// (header + body + footer action). Overlays are only shown embedded in
// richer examples (command palette, dashboard dropdown, select menus, auth
// form, chat), never as bare triggers.
const tiles = [
{ name: 'input', component: PlaygroundInput },
{ name: 'command-palette', component: PlaygroundCommandPalette },
{ name: 'calendar', component: PlaygroundCalendar },
{ name: 'buttons', component: PlaygroundButtons },
{ name: 'notifications', component: PlaygroundNotifications },
{ name: 'transactions', component: PlaygroundTransactions },
{ name: 'transfer-funds', component: PlaygroundTransferFunds },
{ name: 'invite-team', component: PlaygroundInviteTeam },
{ name: 'analytics', component: PlaygroundAnalytics },
{ name: 'pin-input', component: PlaygroundPinInput },
{ name: 'badges', component: PlaygroundBadges },
{ name: 'dashboard', component: PlaygroundDashboard },
{ name: 'report-bug', component: PlaygroundReportBug },
{ name: 'goal', component: PlaygroundGoal },
{ name: 'savings-targets', component: PlaygroundSavingsTargets },
{ name: 'typography', component: PlaygroundTypography },
{ name: 'shortcuts', component: PlaygroundShortcuts },
{ name: 'navigation', component: PlaygroundNavigation },
{ name: 'color-picker', component: PlaygroundColorPicker },
{ name: 'empty', component: PlaygroundEmpty },
{ name: 'announcement', component: PlaygroundAnnouncement },
{ name: 'tabs', component: PlaygroundTabs },
{ name: 'contributors', component: PlaygroundContributors },
{ name: 'auth-form', component: PlaygroundAuthForm },
{ name: 'prompt', component: PlaygroundPrompt }
]

// Lanes follow the CONTAINER, not the viewport — the preview pane changes
// width with fullscreen. Tile count follows lanes (the old per-breakpoint
// reveal) so narrow layouts aren't one endless column.
const scrollArea = useTemplateRef('scrollArea')
const { width } = useElementSize(computed(() => scrollArea.value?.$el))
const lanes = computed(() => width.value >= 1200 ? 4 : width.value >= 900 ? 3 : width.value >= 600 ? 2 : 1)

const REVEAL_COUNTS = [6, 12, 19, tiles.length] as const
const visibleTiles = computed(() => tiles.slice(0, REVEAL_COUNTS[lanes.value - 1]))
</script>

<template>
<UScrollArea
ref="scrollArea"
:items="visibleTiles"
:virtualize="{
lanes,
gap: 16,
estimateSize: 360,
paddingStart: 24,
paddingEnd: 24,
overscan: 0,
getItemKey: (index: number) => visibleTiles[index]!.name
}"
class="playground-grid h-full px-4 sm:px-6"
>
<template #default="{ item }">
<PlaygroundCard>
<component :is="item.component" />
</PlaygroundCard>
</template>
</UScrollArea>
</template>
39 changes: 39 additions & 0 deletions docs/app/components/playground/PlaygroundAnalytics.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<script setup lang="ts">
const extra = useStudioExtraIcons()
const bars = [40, 65, 52, 80, 60, 95, 72]
const days = ['M', 'T', 'W', 'T', 'F', 'S', 'S']
</script>

<template>
<div class="p-4 space-y-4">
<div class="flex items-start justify-between">
<div>
<p class="text-sm text-muted">
Visitors
</p>
<p class="text-2xl font-semibold text-highlighted">
418.2K
</p>
</div>
<UBadge color="success" variant="subtle" size="sm" :icon="extra.trendingUp">
+10%
</UBadge>
</div>

<div>
<div class="flex items-end gap-1.5 h-24">
<div
v-for="(bar, index) in bars"
:key="index"
class="flex-1 rounded-t-sm bg-primary/80 transition-colors hover:bg-primary"
:style="{ height: `${bar}%` }"
/>
</div>
<div class="flex gap-1.5 mt-1.5">
<span v-for="(day, index) in days" :key="index" class="flex-1 text-center text-xs text-muted">
{{ day }}
</span>
</div>
</div>
</div>
</template>
33 changes: 33 additions & 0 deletions docs/app/components/playground/PlaygroundAnnouncement.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<script setup lang="ts">
const appConfig = useAppConfig()
const toast = useToast()
</script>

<template>
<div>
<div class="p-4 flex items-start gap-3">
<div class="flex items-center justify-center size-9 rounded-lg bg-elevated text-primary shrink-0">
<UIcon name="i-lucide-radar" class="size-5" />
</div>
<div>
<p class="font-semibold text-highlighted">
Observability Plus
</p>
<p class="text-sm text-muted">
A faster, natural-language way to explore your data. Monitoring moves to the Pro plan in 2026.
</p>
</div>
</div>

<div class="flex items-center justify-end gap-2 border-t border-default p-3">
<UButton label="Dismiss" color="neutral" variant="ghost" />
<UButton
label="Learn more"
color="neutral"
variant="subtle"
:trailing-icon="appConfig.ui.icons.arrowRight"
@click="toast.add({ title: 'Observability Plus' })"
/>
</div>
</div>
</template>
Loading
Loading