Built for every stack
Each component is a standards-based custom element. Upgrade happens
natively, which means the same <bloom-button>
works in Angular templates, HTMX fragments, and plain HTML pages
with zero glue code.
<button>s, form-associated elements,
focus-visible rings, reduced-motion respect, proper ARIA.
Install
Pick whichever path matches your project. All three load the same artifact under the hood.
// main.ts — import once, use anywhere import '@hazeliscoding/kawaii-ui'; import '@hazeliscoding/kawaii-ui/styles/tokens.css';
<link rel="stylesheet" href="https://esm.sh/@hazeliscoding/kawaii-ui/dist/styles/tokens.css"> <script type="module" src="https://esm.sh/@hazeliscoding/kawaii-ui"></script>
Variants
Five semantic variants covering the typical kawaii/Y2K palette. Each uses a gel gradient with an inner highlight and a colored glow shadow.
<bloom-button variant="primary">Primary</bloom-button> <bloom-button variant="secondary">Secondary</bloom-button> <bloom-button variant="accent">Accent</bloom-button> <bloom-button variant="ghost">Ghost</bloom-button> <bloom-button variant="danger">Danger</bloom-button>
Sizes
Three sizes — sm, md, and lg
— with consistent padding, corner radius, and minimum height.
<bloom-button size="sm">Small</bloom-button> <bloom-button size="md">Medium</bloom-button> <bloom-button size="lg">Large</bloom-button>
States
Disabled, loading, and full-width modifiers. Loading preserves the button's width so layout doesn't jump.
<bloom-button>Default</bloom-button> <bloom-button disabled>Disabled</bloom-button> <bloom-button loading>Loading</bloom-button>
<bloom-button variant="accent" full-width>Full-width accent</bloom-button>
Icons
Two named slots — icon-left and icon-right
— accept any HTML: emoji, SVG, icon-font spans. They're hidden
when empty, so no stray padding.
<bloom-button variant="primary"> <span slot="icon-left">💖</span> Like </bloom-button>
Events
Clicks dispatch a composed bloom-click CustomEvent.
The original MouseEvent is available at
event.detail.originalEvent.
<bloom-button id="cta">Click me</bloom-button> <script> cta.addEventListener('bloom-click', (e) => { console.log(e.detail.originalEvent); }); </script>
Forms
bloom-button is a form-associated custom element.
type="submit" triggers native form submission,
type="reset" resets — just like the built-in button.
<form> <input name="name" required /> <bloom-button type="submit" variant="primary">Submit</bloom-button> <bloom-button type="reset" variant="ghost">Reset</bloom-button> </form>
Badges
Pill-shaped status tags in six kawaii pastel colors. Optional dot indicator pulses gently for live/active states.
<bloom-badge color="pink">Pink</bloom-badge> <bloom-badge color="lilac">Lilac</bloom-badge>
<bloom-badge color="green" dot>Live</bloom-badge>
Avatars
Circular avatars with a decorative outer ring, automatic initials
fallback, and optional status dots. The
watching status gently pulses pink.
<bloom-avatar size="lg" name="Cosmo Bloom"></bloom-avatar>
<bloom-avatar status="online" name="Hazel"></bloom-avatar> <bloom-avatar status="watching" name="Luna"></bloom-avatar>
Avatar stack
Group avatars with a subtle horizontal overlap — hover any one to lift it forward.
<bloom-avatar-stack aria-label="Watch party"> <bloom-avatar size="sm" name="Bubble" status="online"></bloom-avatar> <bloom-avatar size="sm" name="Peach" status="watching"></bloom-avatar> </bloom-avatar-stack>
Cards
Content containers with a soft hover lift and optional
highlighted variant that adds an animated rainbow
sparkle border. Named header and footer
slots.
Default card
A standard card with soft border and hover lift.
Highlighted
Animated rainbow sparkle border for emphasis.
Cozy Movie Night
Picking tonight's feature with your crew. Rated 9/10 so far!
<bloom-card variant="highlighted"> <h3 slot="header">Highlighted</h3> <p>Body content</p> <div slot="footer"> <bloom-button size="sm">Explore</bloom-button> </div> </bloom-card>
Inputs
Form-associated text inputs with labels, hints, error states,
and prefix/suffix slots. Participates
in native <form> submission and reset, just
like bloom-button.
<bloom-input label="Email" type="email" required></bloom-input> <bloom-input label="Name" error="Required!"></bloom-input>
Modals
Overlay dialog with focus trap, Escape handling, backdrop click,
and body scroll lock. The consumer owns the open
attribute — the modal dispatches bloom-close with a
reason.
<bloom-button @click="modal.open = true">Open</bloom-button> <bloom-modal #modal @bloom-close="modal.open = false"> <h3 slot="header">Start a watch party</h3> <p>Body content…</p> <div slot="footer"> <bloom-button variant="primary">Continue</bloom-button> </div> </bloom-modal>
Start a watch party
Pick a friend, pick a night, let fate pick the rest. The modal traps focus inside itself — try Tab and Shift+Tab. Press Escape to close.
Compatibility rings
Animated SVG score rings for 0–100 values. Stroke color crosses thresholds automatically: pink under 50, yellow to 79, lime green at 80+.
<bloom-compat-ring score="92" label="Kindred spirits" context="Based on 18 shared ratings" ></bloom-compat-ring>
// Omit score — custom empty copy via named slot <bloom-compat-ring> <p slot="empty">Rate 3 more together!</p> </bloom-compat-ring>
Real-world combos
Components working together — the whole point of a design system.
3 in the queue · 12 finished
Find shows
42 shows finished · 8.2 avg rating
Playground
Swap gradient tokens at any scope — container, component, or
:root. Click a palette below to retheme these four
buttons without rebuilding anything.
/* Theme scoped to one container — no props, no rebuild */ .sunset { --bloom-gradient-gel-pink: linear-gradient(180deg, #ffe29a, #ff6b9d, #c04e8c); --bloom-pink-600: #c04e8c; }
Dark mode
Set data-theme="dark" on any ancestor
(<html>, <main>, or even a
single section). Tokens swap automatically.
<section data-theme="dark"> <bloom-button variant="primary">Primary</bloom-button> </section>
Theme toggle
A drop-in sun / moon toggle. On click it flips
data-theme on <html>, persists
to localStorage, and fires a
bloom-theme-change event. Respects
prefers-color-scheme on first load.
<bloom-theme-toggle></bloom-theme-toggle> // Listen for theme changes toggle.addEventListener('bloom-theme-change', (e) => { console.log(e.detail.theme); // "dark" | "light" });