blessing-ui
| Install | |
|---|---|
npm install blessing-ui |
|
| Latest Version: | 0.3.0 |
| Vue: | 3 |
| License: | MIT |
| Last Updated: | Oct 11, 2026 |
| Links: | GitHub · npm |
Blessing UI
Vue 3 component library and design tokens. Grey-scale UI with one accent that appears only on what someone has chosen; square, upright plates that lean when you engage them; thin oversized watermark type; opacity-fade hovers. The rules come from a design language; the look was re-derived from one anime's official site, ideas only — the story says which, what was kept, and what was not taken.
Docs, with every component live: ui.blessing.id
- 176 components — from button to data table to chat — styled only through
--bless-*custom properties, no Tailwind dependency - Native platform first:
<dialog>, Popover API,<details>, native form controls,Intldates, scroll-snap — no positioning, date or table library - Accessible defaults: focus rings, ARIA tablist/dialog/tables,
prefers-reduced-motion - ESM, tree-shakable,
vueas the only required peer (@tiptap/vue-3optional, forBlessEditor). One component (BlessButton) costs ~1 KB gzip of JS and ~2.6 KB of CSS (tokens + its own styles); everything is ~64 KB + 27 KB. CSS is split per component and pulled in by the import, so you never pay for styles you don't use
Install
pnpm add blessing-ui vue
import { BlessButton } from "blessing-ui";
That's it — each component brings its own stylesheet, and the tokens come with the first import. To load all styles up front instead (no bundler, or a CDN):
import "blessing-ui/style.css"; // tokens + every component's styles
Fonts are not bundled. Load Roboto (100/300/400/700) and Noto Sans JP yourself, e.g.
<link
href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,700|Noto+Sans+JP:400,700&display=swap"
rel="stylesheet"
/>
Tokens only (no components):
@import "blessing-ui/tokens.css";
.tab[aria-selected="true"] {
background: var(--bless-color-accent); /* the accent marks what is chosen */
}
Usage
<script setup lang="ts">
import {
BlessStage,
BlessSidebarNav,
BlessSection,
BlessList,
BlessListItem,
BlessButton,
} from "blessing-ui";
</script>
<template>
<BlessStage>
<template #sidebar>
<BlessSidebarNav
:items="[{ label: 'News', href: '/news', meta: '04.04', badge: true }]"
active="/news"
/>
</template>
<BlessSection title="News">
<BlessList>
<BlessListItem meta="2019.04.04" badge href="/news/1"
>Blu-ray Disc BOX 発売決定</BlessListItem
>
</BlessList>
<BlessButton href="/news">More</BlessButton>
</BlessSection>
</BlessStage>
</template>
Components
176 components across eight groups. Every one styles itself from --bless-* tokens and ships with a test and a docs page.
Primitives
| Component | Purpose | Key props / slots |
|---|---|---|
BlessSkew |
Plate that leans on attention; its slot stays upright | as, color |
BlessButton |
Button or anchor | variant solid/outline/ghost, color, size, href, disabled, loading; #prefix #suffix |
BlessBadge |
Small label, ink by default | color, scaled |
BlessDash |
Inline 10×1px separator | color |
BlessSeparator |
<hr> / vertical rule, optional label |
orientation, decorative, color, label |
BlessIcon |
SVG slot wrapper | size, label |
BlessText |
Typography helper | as, size, weight, tracking, leading, muted, uppercase |
BlessKbd |
Key combo | keys[] or slot |
BlessThemeToggle |
Light/dark switch or 3-way group | mode switch/group, label, labels |
BlessSpinner |
Loading indicator (role=status) |
size, color, label; BlessButton loading uses it |
BlessEmpty |
Empty state | title, description, variant outline/plain; #icon #title #description default=actions |
BlessButtonGroup |
Joined buttons | orientation, label |
BlessIndicator |
Count / dot badge on any element | value number or true, max, color, position |
BlessTimeline |
Vertical rail of events | items[{title,time,description,color}]; #default="{item}" #dot |
BlessSwap |
Two-face toggle | v-model, label, effect fade/rotate/flip; #on #off |
BlessWatermarkOverlay |
Tiled text watermark over content | text, gap, angle, fontSize, opacity, color |
BlessCircularProgress |
Ring progress | value (omit = spinning), max, size, thickness, color, showValue, format |
BlessLoadingBar + useLoadingBar() |
Top-of-page progress | height, position, color; start() stop() set(p) track(promise) |
BlessSlideTransition |
Height 0 ↔ auto | appear; wrap one v-if element with transition: height |
BlessClientOnly |
Browser-only render | #fallback |
BlessChip |
Compact token, removable | label, icon, image, removable, color, size; emits remove |
BlessMeterGroup |
Multi-segment meter | segments[{label,value,color}], max, legend, format, orientation |
BlessBlockUI |
Blocking overlay | blocked, fullscreen; #indicator |
BlessMockup |
Browser / window / phone frame | type, title, dark |
BlessItem |
Media + title + description + actions | title, description, href, button, variant plain/outline/surface, size; #media #actions |
BlessAvatar |
Image with initials fallback | src, name, size, lean, color |
BlessSkeleton |
Placeholder that breathes (no shimmer) | width, height, lines, avatar |
BlessAspectRatio |
Ratio box | ratio |
BlessProgress |
Bar, determinate or not | value, max, label, showValue, color, size |
BlessAlert |
Inline notice | title, color, live, dismissible, v-model; #icon #title |
Forms
| Component | Purpose | Key props / slots |
|---|---|---|
BlessLabel |
<label> with required mark and hint |
for, required, hint |
BlessInput |
Text-like input | v-model, type, size, invalid, description, error; #prefix #suffix |
BlessInputGroup |
Shared surface: addons + any control | size, disabled; #prefix #suffix (text or BlessButton), default = BlessInput/BlessSelect |
BlessTextarea |
Auto-growing textarea | v-model, rows, autogrow, maxlength, counter, error |
BlessCheckbox |
Native checkbox | v-model (bool or array), value, indeterminate, description |
BlessRadioGroup / BlessRadio |
Native radios | group v-model, label, orientation; radio value, description |
BlessSwitch |
role=switch toggle |
v-model, size, labelPosition |
BlessRating |
Star rating on native radios | v-model, max, icon, size, readonly |
BlessFileInput |
File input + drop zone | v-model File[], accept, multiple, hint, list, invalid |
BlessColorPicker |
<input type=color> + swatches |
v-model hex, swatches[], showValue |
BlessPasswordMeter |
Strength <meter> from rules |
value, rules[{label,test}], labels, showRules |
BlessListbox |
role=listbox, keyboard + typeahead |
v-model (array when multiple), options, rows, label; #default="{option, selected}" |
BlessKnob |
Rotary dial on <input type=range> |
v-model, min, max, step, size, sweep, format, label |
BlessSignaturePad |
Canvas signature | v-model data URL, height, lineWidth, color, type; emits end; exposes clear() isEmpty() |
BlessEditor |
Rich text toolbar for Tiptap | editor (from useEditor), tools[], minHeight; default slot = <EditorContent>; #toolbar #footer. Optional peer @tiptap/vue-3 |
BlessRange |
Two-thumb range | v-model [lo, hi], min, max, step, gap, label, showValue, format |
BlessTimePicker |
<input type=time> + datalist |
v-model "HH:MM", step (minutes), min, max, hour12, seconds, size |
BlessUploader |
Upload queue | url, field, headers, accept, multiple, auto, maxSize; emits upload(item, progress, done, fail) done error; exposes start() clear() |
BlessInputNumber |
Number with ± and Intl format | v-model number, min, max, step, buttons, prefix, suffix, format, locale |
BlessPasswordInput |
Password + reveal + meter | v-model, meter; wraps BlessInput |
BlessInputTags |
Free-text tags | v-model string[], separators, max, duplicates |
BlessFloatLabel |
Floating / in-field label | label, for, variant over/in; wrap a control with placeholder=" " |
BlessFieldset |
<fieldset> + legend |
legend, toggleable, v-model:collapsed, disabled |
BlessInplace |
Click to edit | v-model:active, closable; #display #content="{close}" |
BlessCompare |
Before / after slider | v-model 0–100, orientation; #before #after |
BlessCascadeSelect |
Nested options, one column per level | v-model, options[{value,label,children}], showPath, separator, size; emits select(option, path) |
BlessTreeSelect |
Tree in a popover | v-model id or id[], nodes, multiple, leafOnly, size; emits select |
BlessInputMask |
Pattern-masked BlessInput |
v-model masked, mask (# digit A letter * either), placeholder; emits update:raw |
BlessOrderList |
Reorderable list | v-model items, rowKey, buttons; drag, ↑↓, Alt+arrows; emits move; #default="{item,index}" |
BlessCountUp |
Number that counts up to its value, optionally as odometer reels | value, duration, locale, options (Intl), appear, odometer |
BlessTimer |
Countdown, stopwatch or interval timer with Start / Pause / Reset | mode, duration, phases[{label,seconds}], rounds, autostart, controls, v-model:running; emits finish phase; exposes start pause reset toggle |
BlessGauge |
Read-only gauge with zones, as a half circle or a bar (role=meter) | value, min, max, zones[{to,color,label}], variant arc/linear, label, format |
BlessGantt |
Schedule: task bars on a day axis with progress, dependencies and a today line; draggable when editable |
v-model:tasks [{id,label,start,end,progress,after}], editable, dayWidth, rowHeight, from, to, locale, labelWidth, v-model:selected; emits select change |
BlessShortcutRecorder |
Press-to-record key combo with conflict detection | v-model e.g. Ctrl+K, taken (list or {combo: name}), allowBare, label, placeholder; emits conflict |
BlessDiff |
Line diff of two texts, inline or side by side, with folding | a, b, mode inline/split, context, lineNumbers, labelA, labelB, label |
BlessCodeBlock |
Code listing with copy, line marks, line numbers and file tabs; you bring the highlighter | code, lang, filename, files[{name,code,lang}], v-model:active, lines "2,4-6", lineNumbers, copy, wrap; emits copy; #code="{code,lang,lines}" |
BlessMarquee |
Seamless ticker; pauses on hover, focus and a button; still under reduced motion | speed, reverse, pauseOnHover, controls, v-model:paused, label |
BlessMasonry |
Variable-height cards packed into columns, in reading order | items, rowKey, columns, minWidth, gap, label; #default="{item,index}" |
BlessSplitView |
Master / detail: draggable divider when wide, one pane at a time when narrow | v-model master width %, v-model:detail (stacked), breakpoint, min, max, storageKey, backLabel; emits back; #master="{open}" #detail |
BlessRadialMenu |
Round menu around the pointer: right-click, long touch (slide and release) or the menu key | items[{id,label,icon,disabled}], v-model:open, radius, holdMs, disabled, label; emits select; default slot is the target, #item="{item,index}" |
BlessLoupe |
Magnifier lens over an image: hover, touch-drag or arrow keys | src, alt, zoomSrc, zoom, size |
BlessSliderCaptcha |
Slide the piece into the gap (a human check, not security) | v-model:verified, src, tolerance, label; emits verify({ms}) fail; exposes reset(); ←→ + Enter from the keyboard |
BlessSwipeDeck |
Stack of cards: swipe or press to accept / reject / skip | v-model remaining cards, rowKey, label, controls, threshold, labels; emits decide(item, decision) undo(item); #card #empty; exposes accept reject skip undo |
BlessHotspots |
Numbered pins on an image or diagram, each with a popover | v-model spots [{id,x,y,label}] (percent), v-model:active, src, alt, editable, placement; emits add remove; #spot="{spot,index}", default slot for a diagram |
BlessCropper |
Crop and rotate an image; exports the cropped pixels | src, aspect, crossorigin, controls, label; v-model rect {x,y,width,height,rotation}; toBlob({type,quality,maxWidth}); emits ready error |
BlessKanban |
Board: columns of cards, drag or keys between columns | v-model columns [{id,title,items,limit}], rowKey, label; emits move(item, from, to); #card="{item,column,index}" #header="{column}" |
BlessMention |
Textarea with @ / # suggestions |
v-model, options (list or {trigger: list}), triggers, limit, label; other props go to BlessTextarea; emits select(option, trigger) |
BlessHeatmapCalendar |
Activity grid, one square per day | data {iso: count}, end, weeks, weekStart, levels, max, locale, unit, legend; v-model picked day; arrows move by day / week |
BlessSortable |
Reorder a list or grid by drag handle or keyboard | v-model items, rowKey, label, columns; drag, Space + arrows, Esc cancels; emits move; #default="{item,index}" |
BlessPickList |
Transfer between two listboxes | v-model:source, v-model:target, sourceLabel, targetLabel, rows |
BlessSelect |
Styled native <select> (picker styled too on Chrome 135+) |
v-model, options (groups ok), placeholder, size, error |
BlessSlider |
Native range | v-model, min, max, step, showValue, format |
BlessToggle / BlessToggleGroup |
Pressed buttons | toggle v-model:pressed, value; group v-model, type single/multiple |
BlessCombobox |
Searchable select | v-model, options, multiple, creatable (emits create), size |
BlessDatePicker |
Calendar in a popover | v-model ISO string or [start,end], range, min, max, locale, nativeOnTouch |
BlessCalendar |
Month grid | same as DatePicker plus weekStart, disabledDates, month |
BlessInputOTP |
One-time code cells | v-model, length, numeric, separators, masked; emits complete |
BlessForm / BlessField |
Constraint-API form + field wiring | form emits submit(FormData) / invalid; field label, required, description, error; #default="{ id, error, describedby }" |
BlessNodeGraph |
Editable node-and-link diagram; keyboard linking, acyclic option | v-model:nodes {id,x,y,label}, v-model:edges {from,to}, v-model:selected, v-model:view, snap, acyclic, editable, nodeWidth, nodeHeight, label, labels; #node; connect disconnect move remove |
BlessFormula |
Safe expression field: live result, exact errors, did-you-mean, variable chips | v-model, variables {name,label?,value}, functions, label, placeholder, disabled, locale, labels; result |
BlessRangeCalendar |
Month where drags add or remove days (availability); keyboard painting, blocks summary | v-model string[] (YYYY-MM-DD), month, weekStart, locale, min, max, disabledDates, label, labels; #summary; change |
BlessScheduler |
Day/week time grid; drag to create/move/resize, overlap lanes, keyboard, snap | v-model events {id,title,date,start,end} (minutes), v-model:date, v-model:selected, view, weekStart, dayStart, dayEnd, snap, hourHeight, locale, editable, label, labels; create change remove |
Layout & navigation
| Component | Purpose | Key props / slots |
|---|---|---|
BlessStage |
Page shell, sidebar → drawer under 800px | v-model:open; #sidebar default #footer #background |
BlessSidebarNav |
Vertical nav list | items, active; emits select; #item |
BlessNavigationMenu |
Horizontal nav with panels | items[{label,href,items[]}]; emits select; #panel |
BlessMenubar |
Bar of BlessDropdownMenus |
label |
BlessSteps |
Stepper | steps[{label,description}], v-model index, orientation, clickable; emits select |
BlessTree / BlessTreeItem |
Nested <details> tree |
nodes[{label,id,href,icon,children,open,disabled}], v-model:selected; emits select change |
BlessContainer |
Centred max-width box | size sm/md/lg/full, padded, as |
BlessStack |
Flex row / column | direction, gap token, align, justify, wrap, as |
BlessScrollTop |
Back-to-top button | threshold, target, position, label |
BlessToolbar |
Start / center / end bar | label, surface; #start #center #end |
BlessPanel |
Titled box | title, toggleable, v-model:collapsed, surface; #actions #footer |
BlessStepper |
Steps + panels | steps, v-model, orientation, labels; #default="{step,index,next,prev,last}" #actions; emits finish |
BlessSplitButton |
Button + menu | label, items, color, variant, size; emits click select |
BlessDataView |
List / grid + pagination | items, pageSize, columns, v-model:layout, rowKey; #default="{item,index,layout}" #header #empty |
BlessDeferredContent |
Mount on scroll into view | rootMargin, minHeight; #placeholder; emits load |
BlessTreeTable |
Expandable rows | rows (with children), columns, rowKey, treeColumn, v-model:expanded, defaultExpanded; #cell-* pass through |
BlessOrgChart / BlessOrgChartNode |
Node tree with connectors | nodes, v-model:selected; #node="{node, selected}"; emits select |
BlessBottomTabs |
Mobile bottom nav / dock | items[{label,value,icon,href,badge}], v-model, variant bar/dock, inline; emits select |
BlessSpeedDial |
FAB with fan-out actions | actions[{label,value,icon,color}], direction, color, v-model:open, inline; emits select |
BlessVirtualScroller |
Windowed list (fixed row height) | items, itemHeight, height, overscan, rowKey; exposes scrollTo(i); #default="{item,index}" |
BlessBreadcrumb |
Trail | items[{label,href}], separator; #item |
BlessPagination |
Page list | v-model, total, siblings, href(page) |
BlessTabs |
ARIA tablist | tabs, v-model, activation, color; #default="{tab}" #tab |
BlessSection / BlessWatermark |
Titled block with a giant watermark word | title, watermark, watermarkPosition, headingLevel |
BlessBackground |
Tiled texture layer | src, offset, mode, opacity |
BlessCard |
Flat surface | label, labelColor, surface, bordered, href; #media #footer |
BlessList / BlessListItem |
Divided rows | item meta, badge, href; #suffix |
BlessCollapsible / BlessAccordion(Item) |
<details> based |
v-model:open, title, disabled; accordion type single/multiple |
BlessScrollArea |
Thin scrollbar + fade | axis, fade, height, width |
BlessResizable |
Two-pane split | v-model (% of first pane), direction, min, max, step; #a #b |
BlessTable |
Typed table, stacks on mobile | columns, rows, rowKey, caption, striped, stack; #cell-<key> |
BlessDataTable |
Table + sort/search/select/paginate | columns (+sortable, hideable), rows, rowKey, selectable, searchable, pageSize; #toolbar #cell-<key> |
BlessInfiniteCanvas |
Pan / zoom surface; Ctrl+wheel and pinch zoom, arrows and +/− from the keyboard | v-model:view {x,y,zoom}, minZoom, maxZoom, grid, controls, wheelZoom, label, labels; exposes fit centerOn zoomBy reset toWorld |
BlessDock |
Magnifying icon dock; toolbar keys, links or buttons, badge; off for touch and reduced motion | items {id,label,icon?,href?,badge?,disabled?}, position, size, magnification, radius, label; #item; select |
Overlays
| Component | Purpose | Key props / slots |
|---|---|---|
BlessModal |
Native <dialog> |
v-model, title, hash (deep link), size, dismissible; #footer |
BlessAlertDialog |
Confirm dialog | v-model, title, description, confirmLabel, cancelLabel, color, loading; emits confirm cancel |
BlessSheet |
Side panel | v-model, side, title, size, handle; #footer |
BlessDrawer |
Bottom sheet with swipe-to-close | v-model, title |
BlessPopover |
Popover API panel | v-model:open, placement, trigger click/hover/manual, modal; #trigger #default="{close}" |
BlessTooltip |
Hover/focus tip | text, placement, delay; #content |
BlessHoverCard |
Rich hover popover | placement, openDelay, closeDelay; #trigger |
BlessDropdownMenu |
ARIA menu | items (item/checkbox/radio/separator/label/sub), placement, v-model:checked, v-model:radios; emits select; #trigger |
BlessContextMenu |
Menu on right click | items; wraps its slot |
BlessCommand |
⌘K palette | v-model:open, v-model:history, items (groups, keywords, shortcuts), scopes, recent, recentLimit, persist, labels, inline, hotkey; emits select; #footer |
BlessGallery |
Thumb grid + lightbox | items, v-model index, columns, loop |
BlessConfirmPopup |
Confirm in a popover | message, title, confirmLabel, cancelLabel, color, placement, v-model:open; emits confirm cancel |
BlessDialogHost + useDialog() |
Programmatic modals | open({ title, component, props, text, size, dismissible }) → Promise, close(id, result) |
BlessTour |
Spotlight walkthrough | steps[{target,title,text,placement}], v-model:open, v-model:step, spotlight, labels; emits finish skip |
BlessToaster + useToast() |
Notifications | position; toast(), success(), error(), warning(), info(), dismiss() |
BlessSplash |
First-visit overlay | once, duration, skipLabel; emits done |
BlessConfetti |
Confetti burst fired with fire(from?, angle?); off under reduced motion |
count, colors, duration, spread, power, gravity; done |
BlessCoachmark |
Pulsing new-dot with a bubble; remembers dismissal by id | text, id, placement, dismissOnUse, v-model:dismissed, labels; dismiss |
BlessOnboardingChecklist |
Getting-started checklist: real checkboxes, progress, collapse, dismiss, persisted | items {id,label,description?,action?}, v-model:done, v-model:collapsed, v-model:dismissed, title, persist, floating, dismissible, labels; #complete; select complete dismiss |
Conversation
| Component | Purpose | Key props / slots |
|---|---|---|
BlessMessage |
Avatar + header + content + footer row | name, time, avatar, align start/end, compact; #avatar #header #footer #actions |
BlessBubble |
Message bubble | variant surface/accent/outline/plain, align, collapsible, lines, reactions[]; emits react |
BlessMarker |
Inline status / note / separator / row | variant status/note/separator/border, color, shimmer; #icon |
BlessAttachment |
File / image chip | name, description, src, href, state idle/uploading/error, progress, size, removable; emits remove |
BlessMessageScroller |
Chat viewport | height, initial, threshold; emits reach-top at-bottom; exposes scrollToBottom() scrollTo(id) loadHistory(fn) |
BlessQuestionnaire |
Multi-step single/multiple/freeform | questions[], v-model answers, v-model:step, shortcuts, labels; emits submit skip |
Chart
| Component | Purpose | Key props / slots |
|---|---|---|
BlessChart |
Themed frame for any Unovis chart | title, description, height, legend[]; #header #legend #footer, default = chart |
BlessQrCode |
QR SVG from a module matrix (uqr) | matrix boolean[][], value, size, border, color, background, frame |
BlessSparkline |
Tiny inline trend: line, bar or win/loss | data, type, width, height, color, negativeColor, fill, dot, label, format |
BlessTreemap |
Squarified treemap of a tree of sizes; drill-down path and keyboard | data {id,label,value?,children?,color?}, v-model:path, height, format, label, labels; select |
Blessing does not bundle a chart library. BlessChart maps --bless-color-chart-{1..5}, type and dark mode onto Unovis' --vis-* variables, and adds a legend. Install Unovis yourself:
pnpm add @unovis/vue @unovis/ts
<BlessChart title="Views" :legend="['S1', 'S2']">
<VisXYContainer :data>
<VisLine :x="(d) => d.x" :y="[(d) => d.a, (d) => d.b]" />
<VisAxis type="x" /><VisAxis type="y" />
<VisTooltip /><VisCrosshair />
</VisXYContainer>
</BlessChart>
Any other SVG chart lib works too — read var(--bless-color-chart-N) for series colours.
Mobile
| Component | Purpose | Key props / slots |
|---|---|---|
BlessLayout |
App shell | v-model:left / v-model:right, breakpoint, reveal, leftWidth, rightWidth, swipe; #header #footer #left #right default |
BlessPullToRefresh |
Drag down to reload | threshold, max, scrollTarget; emits refresh(done); #indicator="{ready,busy,pull}" |
BlessSlideItem |
Swipe a row for actions | threshold, sticky; #left #right="{reset}"; emits left(reset) right(reset) slide |
BlessActionSheet |
Actions in a bottom drawer | v-model:open, actions[{label,value,icon,description,danger}], title, grid, cancel; emits select cancel |
BlessTabPanels |
Swipeable panels | v-model, panels[], swipeable, loop, threshold; #default="{panel,index}"; exposes next() prev() |
BlessInfiniteScroll |
Load more at the end | disabled, offset, root, doneText; emits load(done); #loading #done; exposes reset() |
Gestures: usePan(el, cb) · useSwipe(el, cb, { threshold, axis }) · useLongPress(el, cb, { ms }) · useTouchRepeat(el, cb, { delay, interval }). Safe-area tokens --bless-safe-{top,bottom,left,right}.
Media
| Component | Purpose | Key props / slots |
|---|---|---|
BlessImg |
Lazy image | src, alt, ratio, fit, loading, placeholder, srcset, sizes; default slot = caption overlay; #error |
BlessVideo |
Responsive embed / video | src, title, ratio, native, poster |
BlessParallax |
Slow-scrolling background | src, height, speed; #media default |
BlessCarousel |
Scroll-snap slider | v-model, perView, gap, loop, autoplay, dots, arrows |
BlessAudioPlayer |
Track list player | tracks, v-model index, color; emits play pause ended |
BlessAnnotator |
Draw / move / resize boxes and pins on an image; regions as fractions; keyboard | v-model regions {id,x,y,w?,h?,label}, v-model:selected, v-model:tool, src, alt, editable, label, labels |
Composables
useMedia(query?) · reducedMotion() / scrollBehavior(b?) · useHash() · useScrollSpy(ids | selector, { rootMargin, root }) → { active } · useAnimateOnScroll(el, { threshold, rootMargin, once }) → { visible } · useDialog() → { open, close } · gestures (see Mobile) · useResizeObserver(el) · useFullscreen(el?) · useVisibility() · useScroll(el?) · useLoadingBar() · applyMask(mask, input) / unmask(mask, masked) · useTheme() → { theme, isDark, set, toggle, palette, setPalette } · useFloating(anchor, floating, active, { placement, offset, padding, boundary, arrow }) → { x, y, side, arrowX, arrowY, update } · useToast() · useDataTable(rows, { rowKey, pageSize, searchKeys, server, total }) · date helpers toISO fromISO addDays addMonths isoToday.
Types
BlessNavItem, BlessNavMenuItem, BlessTab, BlessGalleryItem, BlessColumn<T>, BlessDataColumn<T>, BlessOption<T>, BlessTrack, BlessMenuItem, BlessCommandItem, BlessCrumb, BlessToastOptions, BlessTheme, BlessStep, BlessTimelineItem, BlessTreeNode, BlessPasswordRule, BlessBottomTab, BlessSpeedDialAction, BlessTourStep, BlessEditorTool, BlessMeterSegment, BlessCascadeOption, BlessDialogOptions, BlessAction, PanEvent, SwipeDirection, BlessUpload, BlessReaction, BlessQuestion, BlessQuestionChoice, BlessAnswers, Placement, DataTableState.
Blocks
Finished sections — a news list, a sign-in form, a dashboard shell — shipped as source you copy into your project, not a package you import. Each is a folder (index.vue, optional parts/, use<Name>.ts, domain.ts, api.ts with an offline mock) that imports only from blessing-ui and itself. Browse them under Blocks in the docs; the contract is in docs/blocks/README.md.
Tokens
All styling reads --bless-* custom properties defined in tokens.css. Override on :root (or any subtree) to theme:
:root {
--bless-color-accent: #4090d0;
--bless-lean: -6deg;
}
| Group | Props |
|---|---|
| Colour | --bless-color-{text,text-muted,bg,surface,surface-2,watermark,accent,accent-soft,accent-2,accent-text,danger,badge,warning,success,info,on-accent,on-text,on-light,border,rule,backdrop,media-bg,chart-1..5} |
| Font | --bless-font-sans, --bless-font-weight-{thin,light,normal,bold} |
| Type scale | --bless-text-{2xs,xs,sm,md,lg,xl,headline,display} (fluid clamp(), 8→151px) |
| Leading / tracking | --bless-leading-{none,tight,normal}, --bless-tracking-{tight,normal,wide,wider} |
| Space | --bless-space-{1,2,3,4,6,8,12,16} (4px base) |
| Shape | --bless-lean, --bless-skew, --bless-skew-counter, --bless-petal, --bless-radius-petal (+-lg), --bless-radius, --bless-radius-plate (0, override points), --bless-border-width, --bless-dash-width |
| Elevation | --bless-shadow-plate (the 1px accent offset behind anything that floats), --bless-shadow-{none,md} |
| Motion | --bless-duration-{fast,base,slow,slower}, --bless-ease-{in-out,out,linear}, --bless-hover-opacity, --bless-press-scale |
| Layout | --bless-sidebar-width, --bless-sidebar-inner, --bless-content-max, --bless-z-{bg,nav,modal} |
Palettes
Megumi's pink is the default accent. Five more — data-palette="utaha" | "izumi" | "michiru" | "eriri" | "tomoya" on <html> — swap only the accent tokens; greys and dark mode are untouched. BlessPaletteToggle switches them; useTheme().setPalette() persists.
Dark mode
Greys invert, the accent family stays, and --bless-font-weight-thin steps 100→300 (hairlines read thinner light-on-dark). Follows prefers-color-scheme by default; set data-theme="dark" / "light" on <html> to force one. useTheme() persists the choice in localStorage (bless-theme) and applies the attribute; BlessThemeToggle is the ready-made control. Override the dark palette under :root[data-theme="dark"] and inside the prefers-color-scheme: dark block in tokens.css.
Durations collapse to 0s under prefers-reduced-motion. Tailwind v4 users can map tokens in @theme { --color-accent: var(--bless-color-accent); }.
Conventions
- Components
Bless*, CSS classesbless-<component>__<part>/--<modifier>, no scoped styles or shadow DOM so overrides are plain CSS. - Colour/size props take token keys, never raw values.
v-modelfor open/selected state. No router dependency:app.provide(blessLinkKey, RouterLink)once and every link-rendering component uses it for in-app paths; otherwise plain<a>.
Development
mise install # node 24, pnpm 12
pnpm install
pnpm dev # docs site (VitePress) with live demos against src/ — http://localhost:5173
pnpm docs:meta # regenerate docs/.vitepress/meta.json from the components (vue-component-meta)
pnpm docs:pages # scaffold a docs page for any component that lacks one
pnpm e2e # cross-browser page-error sweep + axe (needs a running docs preview on :4173)
pnpm e2e:consumer # pack, install into e2e/consumer, strict type-check (no skipLibCheck), tree-shake size gate
pnpm test # vitest + jsdom
pnpm typecheck # vue-tsc
pnpm build # dist/blessing-ui.{js,css} + index.d.ts
pnpm format # oxfmt
Design principles and roadmap: DESIGN.md.
License
Copyright © 2026 DanceInTheFake. Code and design are MIT.
The name of the anime this project takes its look from, its characters and its artwork belong to their respective owners and are not covered by this licence. Blessing UI is an independent fan project — see the story.
Component coverage was checked against the lists of [sha
Related Packages
Adkompas UI — design tokens, Vuetify theme preset, framework-agnostic CSS, and p...
FusionUI — a soft, modern Vue 3 design library. Accessible, token-driven and SSR...
nergous-ui-vue Design System — a zero-dependency Vue 3 component library: themea...
Nuxt module for By Sages Elements — auto-imports the paper-and-ink Vue component...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.3.0 | ^3.5.0 | MIT | |
| 0.2.0 | ^3.5.0 | MIT | |
| 0.1.0 | ^3.5.0 | MIT |