Curated Work / glasswind
Glasswind
A glassmorphism React component library, published on npm.


Overview
Glasswind is a glassmorphism React component library I designed, built, and published to npm — 21 frosted-glass UI components (buttons, inputs, modals, drawers, dropdowns, toasts and more) plus 6 hooks, all styled by a single stylesheet and themeable through CSS variables. It is deliberately a pure frontend package: there is no backend and no database, because the library only ships UI pieces that a developer drops into their own app. The engineering focus was packaging it correctly for the npm ecosystem — dual ESM + CJS builds with TypeScript types and a single CSS file, React as a peer dependency so there is never a duplicate-React clash, and zero runtime dependencies so `npm install glasswind` adds almost nothing to a consumer's bundle. It is live on npm (`npm install glasswind`), open-source on GitHub at github.com/aviralyadav-av/glasswind under MIT, with a live playground deployed on Vercel.
A frosted-glass React component library — 21 accessible, GPU-animated glassmorphism components (buttons, modals, forms, overlays) plus 6 hooks, published to npm as a zero-runtime-dependency, tree-shakeable package that any React or Next.js app can install and import.
Key Features
- 21 accessible glassmorphism components across forms, layout, overlays and utilities, plus 6 reusable hooks.
- Zero runtime dependencies and a tree-shakeable ESM + CJS build with full TypeScript types.
- Themeable entirely through CSS variables — blur, colors, radius, spacing and motion — with built-in dark mode.
- GPU-accelerated animations (transform/opacity only) that hold 60fps and respect prefers-reduced-motion.
- SSR-safe for the Next.js App and Pages Router; plain CSS with no Tailwind requirement.
- Published to npm and installable with a single npm install glasswind.
Why I Built This
I kept reaching for the same frosted-glass UI in project after project and rebuilding it from scratch every time — the blur, the subtle highlight border, the GPU-safe animations. I wanted that look as an install-and-import package the way you reach for Material UI or Chakra, but focused entirely on glassmorphism and with no styling framework required. So I built Glasswind: one npm install, one CSS import, and you have a full set of glass components.
Just as important was doing the un-glamorous packaging work properly. A component library lives or dies on how cleanly it installs — whether its types resolve, whether it tree-shakes, whether it drags in a second copy of React. I treated the npm packaging as the real engineering problem and the components as the fun part on top.
Frontend-only by design — no backend, no database
Glasswind is a 100% client-side library, and that is a deliberate architectural choice rather than a missing feature. It ships UI pieces — React components and the CSS that styles them — and nothing else. There is no server, no API, no authentication, and no database, because a component library has no data of its own: whatever a component displays comes in through props, and transient state like whether a modal is open lives in React memory and is never persisted. The developer who installs Glasswind brings their own backend if they need one; Glasswind just gives them the interface.
Distribution still works without any server I run, because npm is itself the hosting layer: the published package lives in the npm registry, npm install glasswind pulls it from there, and the live demo is a static playground on Vercel. There is simply nothing to keep running.
- Props in, UI out Components are pure presentational pieces — data arrives via props and state is in-memory only, so there is genuinely nothing to store server-side.
- npm as the host The registry serves the package on install, so no backend of my own is needed for distribution.
- Static demo The playground is a static Vite build deployed to Vercel, so even the showcase site is serverless.
Packaging for npm (the real work)
The heart of the project is package.json. Glasswind builds with tsup (esbuild under the hood) into a dist/ folder containing an ESM bundle, a CommonJS bundle, TypeScript declarations, and a single styles.css — all from one build command. A modern exports map tells bundlers exactly where to resolve import 'glasswind' and import 'glasswind/styles.css', while main/module/types fields keep older toolchains happy, and the files array plus an .npmignore guarantee only dist/ (not src/, the playground, or the private docs) ever ships to the registry.
Two rules matter most. React and React-DOM are peerDependencies, never bundled, so a consumer's app uses its own single copy of React instead of clashing with a second one. And the package has zero runtime dependencies, so installing Glasswind adds essentially nothing beyond the code you actually import — combined with sideEffects marking only CSS, it stays fully tree-shakeable.
src/
index.ts # single export: components + hooks + cn
styles/ # tokens.css + base.css (the glass recipe)
components/ # 21 components (each: .tsx + .css + index.ts)
hooks/ utils/
BUILD src/*.tsx --[ tsup ]--> dist/index.js (ESM)
dist/index.cjs (CJS)
dist/index.d.ts (types)
dist/styles.css
SHIP dist/ --> npm registry --> npm install glasswind
playground/ --[ vite ]------> Vercel (live demo)- One build, four outputs tsup emits ESM + CJS + .d.ts + a bundled styles.css in a single npm run build, driven by an exports map so every import resolves correctly.
- React as a peer, not a bundle peerDependencies on react/react-dom prevent the classic duplicate-React clash and keep the package tiny.
- Zero runtime deps, tree-shakeable No runtime dependencies plus a CSS-only sideEffects list means unused components are dropped from the consumer's bundle.
- Only dist/ ships files + .npmignore keep src, the playground and the private docs out of the published tarball; prepublishOnly rebuilds so dist is always fresh.
The Design System & the Glass Recipe
Every component reads from one layer of CSS custom properties, so nothing hard-codes a color, blur, radius or timing — re-theming the whole library is a matter of overriding a handful of variables on the root element. The frosted-glass effect itself is four properties working together: a translucent background, a backdrop-filter of blur() plus saturate() to frost and enrich whatever is behind the element, a 1px highlight border, and a layered shadow with an inner top glow to simulate light catching the edge. A @supports fallback keeps text readable in the rare browser without backdrop-filter.
Class names are BEM with a gl- prefix (gl-btn, gl-modal__header, gl-btn--primary) so Glasswind never collides with a consumer's own CSS. Dark mode is a token swap triggered by data-theme='dark' or a .gl-dark class, with a .gl-auto option that follows the OS colour scheme.
- Tokens, not hard-codes Blur, colors, radius, spacing, motion and z-index all live in tokens.css; components only reference variables, so the whole system re-themes from one place.
- The four-property glass recipe Translucent fill + backdrop-filter blur/saturate + highlight border + layered shadow, with a @supports solid fallback for unsupported browsers.
- Collision-proof naming A gl- BEM prefix on every class means dropping Glasswind into an existing app never clobbers its styles.
Animation Philosophy: no lag
Glasswind only ever animates transform and opacity. Those two are composited by the GPU and never trigger layout or paint, so motion stays at 60fps even on modest hardware — the accordion opens with a pure-CSS grid-template-rows transition instead of measuring heights in JavaScript, and the progress bar fills with a scaleX transform rather than animating width. Nothing animates width, height, top, left or shadow size in a way that would force reflow, and all motion respects prefers-reduced-motion.
- Composited properties only transform and opacity are GPU-composited, so animations never cause layout thrash.
- CSS over JS measuring Accordion uses grid-template-rows and progress uses scaleX — smooth, reflow-free, and no JavaScript height math.
- Reduced-motion aware Every transition honours prefers-reduced-motion for users who ask for less movement.
Accessibility & SSR
The components are built to be usable by keyboard and screen reader, not just to look good. Overlays like Modal and Drawer are portalled, scroll-locked and focus-managed with Esc and backdrop close; menus and tabs support arrow-key roving focus; tooltips open on focus as well as hover; and every interactive element has a visible focus ring and appropriate ARIA roles. An SSR-safe Portal and an isomorphic layout-effect hook mean the overlay components render correctly in Next.js without hydration errors — they are simply marked as client components where interactivity requires it.
- Keyboard-first overlays Focus trapping, scroll lock, Esc/backdrop close and roving arrow-key focus come built in, not bolted on.
- ARIA + focus rings Roles, labels and visible focus states ship by default, so the components are accessible out of the box.
- SSR-safe A Portal and an isomorphic layout effect keep Next.js App/Pages Router rendering free of hydration errors.
Key Decisions
- An npm package over a copy-paste kit — Publishing to npm makes Glasswind installable and versionable the way real libraries are, instead of asking people to paste files, at the cost of getting the packaging exactly right.
- Plain CSS over Tailwind — Bundling one stylesheet means Glasswind works in any project without a Tailwind setup or config, so a single import is all a consumer needs.
- React as a peer dependency — Never bundling React avoids the duplicate-copy clash that breaks hooks, and keeps the package weightless.
- Zero runtime dependencies — Refusing runtime deps keeps installs tiny and tree-shaking clean, which matters more for a library than saving myself a little code.
- Design tokens over hard-coded styles — Routing every value through CSS variables made theming and dark mode a variable swap instead of a component rewrite.
- Animate only transform/opacity — Constraining animation to GPU-composited properties guaranteed 60fps everywhere rather than chasing jank per component.
What I Learned
- npm names normalize — The registry blocks names that collapse to an existing one once punctuation is removed, so npm view returning 404 does not guarantee you can publish — I hit this a few times before landing on glasswind.
- Packaging is the product for a library — The exports map, peer deps and tree-shaking flags decide whether a library is pleasant or painful to use; that config was more consequential than any single component.
- Constraints buy performance — Deciding up front to animate only transform and opacity removed whole categories of jank instead of debugging them one component at a time.
- Tokens make a system — Once every color and size lived in a variable, dark mode and re-theming stopped being features to build and became a value to override.
Tech Stack
Like what you see?
Explore more work or get in touch.
