@mkbabb/glass-ui
v10.1.1
Published
Glassmorphic design system — Vue 3.5 components, reka-ui primitives, Tailwind CSS v4 tokens
Readme
glass-ui
Glassmorphic design system for Vue 3.5. Shared components, design tokens, and composables built on reka-ui and Tailwind CSS v4, with a golden-ratio typography scale.
Install
npm install @mkbabb/glass-uiUsage
// vueuse-FREE root barrel — core primitives + core composables
import { Button, Card, Dialog } from "@mkbabb/glass-ui";
// vueuse-bearing surfaces ship as flat subpaths (the SCC-trap closure)
import { useGlobalDark } from "@mkbabb/glass-ui/dark";
import { useKeyboardShortcuts, registerShortcut } from "@mkbabb/glass-ui/keyboard";
import { useCarousel } from "@mkbabb/glass-ui/carousel";
import { Input, Textarea, Combobox } from "@mkbabb/glass-ui/forms";
// per-package subpaths — one component family per dist chunk
import { GlassDock, DockLayerGroup, DockLayer } from "@mkbabb/glass-ui/dock";
import { DarkModeToggle } from "@mkbabb/glass-ui/dark-mode-toggle";
import { Configurator, useConfiguratorState } from "@mkbabb/glass-ui/configurator";
import { Popover, PopoverTrigger, PopoverContent } from "@mkbabb/glass-ui/popover";
// public types + constants live on their OWNING subpath (the 5.0.0 export reshape
// dropped the `/api` discovery layer — every symbol re-homes to its family subpath)
import type { AuroraConfig } from "@mkbabb/glass-ui/aurora";
import type { ButtonEmphasis, ButtonProps } from "@mkbabb/glass-ui/button";
import type { CardTier } from "@mkbabb/glass-ui/card";
import { DEFAULT_AURORA_CONFIG, MAX_NUCLEI } from "@mkbabb/glass-ui/aurora";<Popover trigger="hover" :open-delay="120">
<PopoverTrigger as-child>
<Button emphasis="primary" size="lg">Run audit</Button>
</PopoverTrigger>
<PopoverContent>Quicker reveal for chassis actions</PopoverContent>
</Popover>@import "tailwindcss";
@import "tw-animate-css";
@import "@mkbabb/glass-ui/styles";
@import "@mkbabb/glass-ui/styles/fonts";
@source "../node_modules/@mkbabb/glass-ui/dist"; /* template-utility content-scan */
@variant dark (&:where(.dark, .dark *));
/* override tokens locally for your project */
:root {
--glass-opacity-resting: 0.82;
/* the consumer-tunable radius primitive — the composed --glass-blur-resting
threads --glass-level + saturate, never override it directly. The shipped
resting/quiet default is 7px (the unified base material); re-pin here to retune. */
--glass-blur-resting-radius: 7px;
}The optional styles/fonts entry carries the packaged Plus Jakarta Sans and
Fira Code faces as self-contained WOFF2 data URLs. Consumers that need raw font
files can address the same built assets through @mkbabb/glass-ui/fonts/*.
Every packaged face is roman: there is no italic @font-face and none is
intended. Three utilities ask for one anyway — text-math, text-math-body and
fourier-f, because mathematical italic is notation — so they are
SYNTHESIS-DEPENDENT: the browser obliques the roman face for them. The library
declares no font-synthesis, so the UA default (auto) applies and they paint.
If your app sets font-synthesis: none at the root, those three lose their slant.
text-caption is upright; add italic beside it if you want the slant back.
Documentation
The authoritative canon lives under docs/canon/, with the
consumer-facing migration path in MIGRATION.md:
docs/canon/structure.md— thesrc/tree mapdocs/canon/glass-system.md— the glass tier ladder + adaptive-legibility axesdocs/canon/exports-and-subpaths.md— the public-surface layering + themanualChunksrecipedocs/canon/dependencies.md— the peer set + the keyframes/value.js spinedocs/canon/conventions.md— TypeScript / token / ring conventionsdocs/canon/motion-system.md— the spring/bezier motion canonDESIGN.md— the storybook category index
Of that, only MIGRATION.md ships. package.json declares files: ["dist", "MIGRATION.md"], so an install carries dist/, MIGRATION.md, this README and the licence — nothing else; the migration guide for the version you pinned is at node_modules/@mkbabb/glass-ui/MIGRATION.md. Read DESIGN.md and docs/ on GitHub.
Build
npm run dev # storybook demo (multi-page, dock + carousel navigation)
npm run build # library → dist/glass-ui.js + glass-ui.css + index.d.ts + per-subpath chunks
npm run typecheck # vue-tsc --noEmit
npm run profile:bundle # inspect bundle sizes and exact value.js capability importsStorybook
npm run dev launches a Vue 3 storybook under demo/ covering every primitive, container, navigation element, data component, feedback pattern, motion demo, composable, and composition. A vertical GlassDock rail drives category navigation; a horizontal Carousel pager drives stories within a category. Keyboard: [/] prev/next story, {/} prev/next category, , configurator, ? keyboard help. A dismissible right-side Sheet lets you live-edit font/scale/hue/grain/radius/density/cartoon-shadow/dark tokens against :root. See DESIGN.md#storybook-demo for the full category index.
Structure
The source tree follows the concise ownership map in docs/canon/structure.md:
src/
├── index.ts # curated root barrel
├── forms.ts # input-family entry
├── components/ # flat, owner-colocated component families + _shared
├── composables/ # shared composable families
├── styles/ # global tokens, glass substrate, and utilities
├── fonts/ # packaged font assets
└── html-attributes.d.tsSubpath imports
Beyond the vueuse-free root barrel, the library ships flat, independently
tree-shakable JS subpaths plus CSS and font entries. The semantic entry graph in
scripts/lib/subpath-policy.mjs feeds the build and generated package.json
exports; scripts/verify-export-types.mjs checks the emitted declaration paths.
// vueuse-bearing subpaths (the SCC-trap closure — the root barrel does not
// re-export these symbols)
import { useGlobalDark } from "@mkbabb/glass-ui/dark";
import { useKeyboardShortcuts, registerShortcut } from "@mkbabb/glass-ui/keyboard";
import { useCarousel } from "@mkbabb/glass-ui/carousel";
import { Input, Textarea, Combobox } from "@mkbabb/glass-ui/forms";
// per-package subpaths — substrate isolation
import { GlassDock, DockLayerGroup, DockLayer } from "@mkbabb/glass-ui/dock";
import { DarkModeToggle } from "@mkbabb/glass-ui/dark-mode-toggle";
import { Aurora, useAurora } from "@mkbabb/glass-ui/aurora";
import { Configurator, useConfiguratorState } from "@mkbabb/glass-ui/configurator";
import { Popover, PopoverTrigger, PopoverContent } from "@mkbabb/glass-ui/popover";
import { InstrumentChassis } from "@mkbabb/glass-ui/instrument-chassis";
import { useSidebarState } from "@mkbabb/glass-ui/sidebar";There is no /clipboard subpath. useClipboard and writeClipboard ship on /dom and on
the root barrel; useClipboard returns { status, copy, invalidate }, where status is a
ComputedRef<ClipboardStatus> — the copied boolean was removed at 7.0.0, and the
equivalent is status.value === "success".
The v0.9.x nested subpaths @mkbabb/glass-ui/composables/dark and
.../composables/keyboard were flattened to /dark and /keyboard at v1.0.
The demo-only /pagination and /virtual subpaths are retired. The 5.0.0 export
reshape also dropped the pure-types /api discovery layer; see MIGRATION.md
for the owning-family re-home table.
Glass Token System
Five tiers with 1:1 alignment across opacity, blur, background, border, and shadow. Each tier defines --glass-{opacity,blur,bg,border,shadow}-{tier}; consumers override the primitives at :root to tune intensity. --glass-level (a typed inheriting @property) is the ONE opacity+blur knob — level=1 is the hand-tuned ladder, level=0 is the opaque escape.
| Tier | Class | Use |
| -------- | ----------------- | --------------------------------------------- |
| Wash | .glass-wash | Dock backgrounds, input fills, hover overlays |
| Quiet | .glass-quiet | Inline workspace chrome |
| Resting | .glass-resting | Cards, content containers |
| Floating | .glass-floating | Popovers, tooltips, dropdowns |
| Overlay | .glass-overlay | Dialogs, command palette, modals |
Convenience classes bundle a tier with a shape: .glass-card (resting + --radius-card + offset card shadow) and .glass-pill (resting + pill radius + press feedback). Commands use <Button> so geometry, semantics, focus, and press behavior retain one owner. See docs/canon/glass-system.md for the adaptive-legibility axes (--glass-tint-*, --glass-accent, the dark transmissive-material arm).
Design Tokens
src/styles/tokens/ defines the shared :root properties consumed by all style modules and components — duration, easing (--spring-{smooth,snappy,bouncy,gentle} + --spring-<name>-duration clocks), z-index, radius (primitive + semantic), shadows (composed via color-mix(in srgb, var(--shadow-color) N%, transparent)), the 5-tier glass ladder, paper/grain textures, the --surface-tint-* family, and the warm-chroma color palette. These are declared in a plain unlayered :root, which beats every layered consumer :root: re-declare one in an unlayered :root of your own, or on an element below :root in any layer, and every reader downstream follows; a :root inside @theme or any @layer loses to the package root. Font registers are the one exception and live in the theme layer — re-declare --font-serif-math in your own @theme, and rebrand --font-text / --font-display there or through --font-stack-text / --font-stack-display on an unlayered :root (MIGRATION.md, The token-root contract under 10.0.0). [2026-09-22 · O-23/O-32 B-1 — this sentence said any re-declaration in your own CSS wins; a layered :root does not.]
Every other rule the package ships sits in @layer components, and ./styles.css opens with @layer theme, base, components, utilities; (the same order ./styles declares), so a rule of yours — unlayered, or in @layer utilities — beats a library rule at any specificity. What stays outside the layer: the token roots above, the @utility / @theme / @property / @font-face / @keyframes at-rules, and one named exception, the slider's stylesheet. !important runs the other way across layers: a library !important beats yours unless yours sits in a layer ordered before components.
Bridge names are not tokens, and overriding one does nothing. src/styles/theme/bridges.css is a single @theme inline block mapping Tailwind theme keys onto those tokens: --shadow-glass-*, --color-*, --text-*, --spacing-icon-*, --blur-glass-* and nine more families (--z-*, --font-*, --leading-*, --tracking-*, --transition-*, --ease-* among them). Their job is to generate utilities — shadow-glass-quiet, blur-glass-wash, size-icon-md — with the referenced value substituted in place, and inline is what stops them minting a second spelling of the same value. A theme key reaches your built CSS as a custom property only when something in that build reads it with var(); nothing in this library's own build reads the shadow, blur or icon bridges, so none of them emits here; the shadow family also measures zero in four independently compiled consumer bundles. var(--shadow-glass-quiet) therefore resolves to nothing. Override the tokens/ spelling instead:
/* the override point — a plain :root token, always emitted */
:root {
--glass-shadow-quiet: 0 1px 3px rgb(0 0 0 / 0.08);
--glass-shadow-resting: 0 4px 12px rgb(0 0 0 / 0.1);
}Five of the six shadow rungs are bridged: --glass-shadow-capsule has no --shadow-glass-capsule and no utility, only the token.
The easing seam. <EasingCurve> writes --easing-curve-accent: var(--motion-accent, var(--viz-legendre)) on its own wrapper, so that name is already bound on the curve's subtree and an ancestor's --easing-curve-accent never reaches it. The one free arm is --motion-accent: set it on or above the curve to re-point the accent.
Typography
Type scale based on √φ ≈ 1.272 (modulated golden ratio); each step is φ^(n/2) of the base. Semantic classes run .text-micro → .text-body → .text-heading → .text-title → the audacious .text-display-* ladder. The @theme block in theme.css maps these to Tailwind's --font-size-* tokens, so text-sm / text-lg adopt the golden-ratio scale.
Target size
Three mechanisms carry the WCAG-2.5.5 44px floor, and a consumer reaches all
three. --control-floor is the clamp seam in the height cohort: 0px at
desktop, lifted to --touch-target (2.75rem) under pointer: coarse, and
every --control-h-xs|sm|md|lg rung is a max() against it, so no --ui-scale
below 1 can drop a control under the floor. [data-control-target] is the
attribute opt-in — one shared rule in styles/utilities/responsive.css floors
both axes of any element wearing it under coarse pointer, and you can put it on
your own interactive faces. .control-bit is the binary-control register
(checkbox · switch · radio): the host itself is the ≥44 seat, in flow, and the
paint sits on the smaller .control-bit__face child — so the rect the pointer
hits is the rect getBoundingClientRect reads, and the paint stays small.
Conventions
- TypeScript
strict:true,verbatimModuleSyntax:true;moduleResolution:bundler,target:ES2022,lib:ES2023 import typefor all type-only imports; named exports only, no defaults- reka-ui primitives for polymorphic/accessibility behavior; typed semantic props and owner-colocated CSS for component appearance
- Color tokens are complete
hsl()colors —--primary: hsl(24 10% 10%), consumed directly asvar(--primary). NEVERhsl(var(--token))(double-wrapping is invalid and never paints); for an alpha derivative usecolor-mix(in srgb, var(--token) N%, transparent) cn()normalizes Vue-compatible class values and applies a small conflict-bucket deduplicator
See docs/canon/conventions.md for the full set.
Dependencies
All runtime deps are peer — glass-ui declares them in peerDependencies and ships none in its own dependencies bundle, so the consumer's single Vue / Tailwind / reka-ui spine is reused rather than re-vendored.
| Package | Role |
| ----------------------------------- | ----------------------------------------------------------------- |
| vue ^3.5 | Framework |
| reka-ui ^2.0 | Headless UI primitives |
| tailwindcss ^4.0 | Utility CSS |
| @lucide/vue ^1.16.0 | Icon set (the renamed v1 package; was lucide-vue-next pre-v1.0) |
| @mkbabb/keyframes.js ^6.0.0 | Spring/keyframe runtime |
| vue-component-type-helpers ^3.0.3 | Emitted-declaration helper types |
| @vueuse/core ^14.0 | useDark, createGlobalState, useEventListener (optional peer) |
| @mkbabb/value.js ^4.0.0 | Color and easing capabilities (optional peer) |
| tw-animate-css ^1.2.5 | animate-in/animate-out data-state utilities (optional peer) |
tw-animate-css is required only for the animated overlay surfaces (Dialog / Sheet / Popover / DropdownMenu emit animate-in/animate-out data-state utilities); a Button-only consumer never needs it. See docs/canon/dependencies.md.
Contributing
See CONTRIBUTING.md. The README shape follows the perimeter-level canonical README shape.
License
MIT © 2026 Mike Babb.
