# bitboss-ui — core knowledge base (medium tier) > Vue 3 component library by BitBoss. This single file is the mandatory > reading before writing any bitboss-ui markup: the hard rules, setup, > how to choose a component, and the design language — plus a catalogue > of every public component. Canonical URL: https://cdn.jsdelivr.net/npm/bitboss-ui@beta/dist/llms-medium.txt Need more? Fetch the specific component contract you need — one hop, not the whole catalogue: https://cdn.jsdelivr.net/npm/bitboss-ui@beta/dist/ai/.md Machine-readable API surface: https://cdn.jsdelivr.net/npm/bitboss-ui@beta/dist/ai/components.json --- # AI Router Read this file first. Open **at most one** deeper file per hop. Do not load every recipe or every component contract into context. Knowledge base root (installed package): `node_modules/bitboss-ui/dist/ai/` Package export entry: `bitboss-ui/ai` → `dist/ai/index.md`. Subpaths: `bitboss-ui/ai/components.json`, `bitboss-ui/ai/guides/…`, etc. **Not installed, or can only fetch URLs?** The same files are served from the published package at `https://cdn.jsdelivr.net/npm/bitboss-ui@beta/dist/ai/…`. If you can make only one request, fetch [`…@beta/dist/llms-medium.txt`](https://cdn.jsdelivr.net/npm/bitboss-ui@beta/dist/llms-medium.txt) — this router plus the agent contract, setup, component picker, design language and the full component catalogue, in ~110 KB. --- ## Mandatory order (every UI task) 1. **[Agent contract](./agent-contract.md)** — hard rules (Bb\* boundary, setup-first, `href`/`to` navigation, icons, coherence). 2. **Setup?** If the project may not have the plugin yet → [Installation & Plugin Setup](./installation-and-plugin-setup.md). **Set `locale` explicitly if the app is not Italian** — the plugin defaults to `'it'` and there is no build-time warning for leaving it unset. 3. **Choose intent:** - Whole page / flow → hop to **Recipes** below (one platform file only). - Single control → [Component picker](./component-picker.md), then open **one** `../.md` contract (Usage & Guidelines first). 4. **Never invent props.** Confirm names in that contract or in [`../components.json`](../components.json). --- ## Recipes (page-level — pick one platform) Folders: `../recipes/vue/`, `../recipes/nuxt/`, `../recipes/inertia/`. Same page shapes on each platform; only the data/mutation layer differs. | Intent | Recipe file (kebab) | | ------------------------------------ | ----------------------------------------- | | Filterable listing + bulk/peek | `records-workspace.md` | | Create/edit form | `record-form.md` | | Inline-edit table / backlog | `inline-edit-workspace.md` | | Hierarchy drill-down (nested tables) | `ownership-atlas.md` (Inertia only today) | | Approvals / triage inbox | `approvals-inbox.md` | | Uploads + editable queue | `upload-center.md` | | Multi-step wizard | `wizard-form.md` | | Onboarding checklist | `onboarding.md` | No recipe for detail-with-tabs, settings, dashboards, or profile pages — compose those from the closest listing/form recipe plus the guides (page shell, tabs, inline-edit playbook) instead of inventing a new shape. --- ## Playbooks (only when the pattern applies) | Need | Guide | | -------------------------------------- | ----------------------------------------------------- | | Tied selects / stale model cleanup | [coherence-playbook](./coherence-playbook.md) | | Async `items` providers / prefill | [fetch-items-playbook](./fetch-items-playbook.md) | | `item-text` / `item-value` / disabled | [options-items-playbook](./options-items-playbook.md) | | Table cell edit ownership | [inline-edit-playbook](./inline-edit-playbook.md) | | Table row → detail (identity cells) | [agent-contract](./agent-contract.md) § Inline Links | | Peek that drills into related records | [agent-contract](./agent-contract.md) § Consult-Stack | | Icons (`lucide:*`, never invent names) | [icons-policy](./icons-policy.md) | | Tokens / styling around components | [design-language](./design-language.md) | | Upgrading from bitboss-ui 2.x | [migration/v2-to-v3](./migration/v2-to-v3.md) | --- ## Full catalogue (last resort) - [Component index](../index.md) — every consumer + reference-only entry - [components.json](../components.json) — machine-readable API for validation - Composables: `../composables/` (`useToast`, `useConfirm`, table/tabs context, …) Reference-only (`BbBase*` form primitives, listbox internals, …) — **do not import in consumer apps** unless the user explicitly asks for internals. Exception documented in the agent contract: **`BbBaseButton` is consumer API**. --- ## Consumer kickstart In the app repo (once), after installing `bitboss-ui`: ```bash npx bitboss-ui ai-init # optional: also register the MCP server (its peers are not installed with the library) npm i -D @modelcontextprotocol/sdk zod npx bitboss-ui ai-init --mcp ``` Writes harness-agnostic pointers (`AGENTS.md`) plus Cursor / Copilot / Claude / Windsurf rules so agents find this router without being told. After writing Bb\* markup, validate against the installed contract: ```bash npx bitboss-ui check # or: npx bitboss-ui check 'src/**/*.{vue,md}' --json ``` Optional MCP (`npx bitboss-ui mcp`): search/get components, list/get recipes, validate snippets — version-matched to the installed package. Needs the optional peers `@modelcontextprotocol/sdk` + `zod` (~12 MB, used by nothing else); the command names them if they are missing. --- # Agent Contract Use this contract before generating any UI code with `bitboss-ui`. ## Default API Boundary 1. Prefer `Bb*` components in consumer-facing code. 2. Treat low-level primitives (`Base*`, `BbBase*`, `Common*`, listbox internals) as reference-only — **except `BbBaseButton`**, which is a first-class consumer component: the unstyled navigable primitive for any clickable surface (cards, rows, custom links) that isn't a variant-first `BbButton`. 3. Use the other reference-only components only when the user explicitly asks for internal composition details. ## Setup-First Rule Before writing component usage code, verify: - `bitboss-ui` is installed; - the build plugin is configured (`bitboss-ui/vite` in Vite/Inertia apps, `bitboss-ui/nuxt` in Nuxt apps) — it serves the `virtual:bitboss-ui/icons` and `virtual:bitboss-ui/config` modules; - the runtime plugin is installed (`app.use(bitbossUiPlugin)`; the Nuxt module does this for you), which injects `bitboss-ui/styles.css` unless `injectStyles: false`. - **the plugin's `locale` option is set, if the app is not Italian.** It **defaults to `'it'`** — every built-in string (date formats and per-segment labels on the date/time pickers, select-all text, `useConfirm`'s OK/Annulla labels, and more) ships Italian until `locale` is set explicitly. There is no build-time warning for an unset `locale`, only for an unknown one, so this ships silently. Set `locale: 'en'` (or the target locale) in `vite.config.*` / `nuxt.config.*`, and if the app switches locale at runtime, list every locale it can switch to in `enabledLocales` up front — narrowing `locale` alone narrows the bundle and makes any other locale a compile error at `useBbConfig().setConfig({ locale })`. Details: [Installation and Plugin Setup](./installation-and-plugin-setup.md) § `locale`. If setup is missing, provide setup code first ([Installation and Plugin Setup](./installation-and-plugin-setup.md)), then component examples. ## Knowledge Base: Router First, Then Curated Guides Read the installed package knowledge base before recommending components: 1. **`dist/ai/guides/ai-router.md`** — tiny hop map (which file to open next). 2. **`dist/ai/index.md`** — full catalogue only when the router is not enough. Every component doc (`dist/ai/.md`) **leads with a "Usage & Guidelines" section** — a curated, runtime-tested guide with live examples. Read it before the raw props/events/slots tables, and prefer its idioms over anything you would infer from the API surface alone. If a guide shows one way to do something, that is the idiomatic way. Consumer projects should run `npx bitboss-ui ai-init` once so AGENTS.md / Cursor / Copilot / Claude / Windsurf all point at this corpus. ## Navigation Rule Offload navigation to the component. The navigation engine lives in `BbBaseButton`, the primitive every button wraps — so `BbButton`, `BbDropdownButton`, `BbBaseButton` itself, and every item-driven surface built on the same core (dropdown items, breadcrumb items, tabs items, pagination in `navigation` mode) all navigate by themselves. Push _as much navigation as possible_ onto this surface — any click whose result is a destination belongs here, not in an `@click` handler: - `href` — renders an anchor; in an Inertia app it becomes an Inertia visit and accepts `method`, `data`, `headers`, `preserveScroll`, `replace`, …; - `to` — vue-router `RouteLocationRaw`, rendered as a router link (tabs items use `href`; pagination `navigation` builds per-page `to`/`href` links itself). **NEVER** write `@click="router.push(...)"` (or `window.location = ...`) for something the component can navigate to itself. Click handlers are for actions; destinations go in `href`/`to`. ## Inline Links & Identity Cells Rule `BbButton` is **not** an inline link. It is a full button with a box — height and horizontal padding from the size scale — and `variant="link"` only **recolors** that box (it does not remove the padding or let it flow inline). Dropped into running text, a table cell, or a property grid, that button box **breaks the line height and text flow** around it. The inline link/button is **`BbBaseButton`**: zero chrome, the same `href`/`to`/Inertia navigation engine, and its look is **owned by the project's CSS** — e.g. a `.plain-link` class that adds only an underline. Use it for: - a clickable **record-identity cell** (a row's name/title column) that must truncate and flow inline at the cell's width; - any **in-text link** inside a table, peek panel, or property grid; - a link whose appearance is a plain text link, not a button. Rule: **inline text link → `BbBaseButton` + your own link class; never `BbButton` (including `variant="link"`).** Reserve `BbButton variant="link"` for a standalone, button-spaced navigation control sitting among other buttons ("See pricing", "Forgot password?"). See [BbButton](../BbButton.md) § Choosing a variant and [design-language](./design-language.md) § the `.plain-link` helper. ## Consult-Stack Rule (records listings) A listing screen with a peek sidebar is for **consulting** — invoice → its account → another invoice on that account → back — without leaving the list. The shell's stack exists for exactly that, and it is opt-in, so agents keep shipping listings where every row opens one reset peek and every related name inside it is dead text. Each follow-up question then costs a full-page navigation, which is the thing the shell was supposed to remove. Rule: **on any records/approvals/ownership listing, related names are navigable and the peek stacks.** Concretely: 1. `PeekItem` is one discriminated union covering every related type the rows and the peeks expose — not a second shell per type. 2. Row click → `peek(item)` (fresh top-level). Related name → `stackItem(item)` (drills, keeps the back arrow). Prefer these named intents over `push(item, true)`, whose boolean is what people get backwards. 3. Every related name is a `BbBaseButton` + `.plain-link` (see the rule above), in table cells **and** inside peek bodies. 4. `#sidebar-title` follows the top of the stack; the row highlight follows its root, so drilling does not un-highlight the row the consult started from. See [page-shell](./page-shell.md) § The consult stack and the records-workspace recipe's conformance checklist. ## Item-Driven Components Rule For components accepting `items`: - prefer declarative mapping with `item-text` and `item-value`; - use `dependencies` and `deps-debounce-time` for tied fields; - use `enforce-coherence` instead of custom watchers for stale model cleanup. ## Icons Rule External-provider-first: prefer iconify provider icons — default set `lucide:*` — sourced from `@iconify-json/*` packages the consumer installs separately (`npm i -D @iconify-json/lucide`). Use `local:*` SVGs only for brand-specific art placed in the configured `iconDir`. Only reference icons from installed providers or existing local files — **never invent icon names**. Full workflow: [Icons Policy](./icons-policy.md). ## Variants Rule Unlike icons, variants are **not** a fixed vocabulary you must stay inside. The shipped names (`primary`, `secondary`, `destructive`, `outline`, …) are the **floor**; the registries are open on purpose so an app can add the tones its design needs. When a design calls for a colour the library does not ship — soft semantic status badges, a brand button tone — **add it, in both halves**: 1. **Register the name** in the build plugin: `badgeVariants`, `buttonVariants`, `alertVariants`, `confirmVariants`, `dropdownItemVariants`, `tooltipVariants`, `toastVariants`, `indicatorVariants` (`bitboss-ui/vite` or `bitboss-ui/nuxt`). The plugin regenerates the typed registry, so the name type-checks. 2. **Style the class** — `.bb-badge.bb-badge--`, `.bb-button--`, … — in a project stylesheet loaded **after** `bitboss-ui/styles.css`. The library ships no CSS for names it does not own. Half the job is a bug in both directions: a registered name with no CSS renders an unstyled pill (the original "warning badge" hallucination), and CSS with no registration fails `vue-tsc`. Equally, **do not downgrade the design** — falling back to `secondary`/`outline` because a status palette "isn't available" is the wrong fix; registering it is a two-file change you are expected to make. Never write a variant name that is neither shipped nor registered. Canonical soft status palette (accents, the shared treatment, and why the selectors are doubled): [Design Language](./design-language.md) § Getting soft semantic badges. Where the option goes: [Installation and Plugin Setup](./installation-and-plugin-setup.md). ## Overlay Open-State Rule Overlay visibility is controlled by the component's **primary `v-model`** — never a separate `v-model:open`. The standalone overlays (`BbDialog`, `BbOffCanvas`, `BbPopover`, `BbTooltip`) expose a boolean `v-model` for open state. The popover-embedded surfaces (`BbDropdown`, `BbSelectPopover`, `BbColorPalette`) keep open state **internal** — drive them through their trigger/activator, not a bound open flag. There is no `v-model:open` anywhere in the library, by design; do not reach for one. These overlays also return focus to their trigger element on close, by documented default — a consumer `@focus`-reopen handler on that trigger will fight the restore and can loop. ## Global Host Rule `useToast()` and `useConfirm()` write to **module-level singleton state**, not to a component instance. `` and `` are the only things that render that state, and each mounted host renders _all_ of it. **Mount exactly one `` and one `` for the whole application, at the app root** — `App.vue` under Vue, `app.vue` under Nuxt, the `createInertiaApp` render root under Inertia. Never one per page, per route, per layout branch or per component; a page that calls `toast()` or `confirm()` mounts nothing. - **Zero hosts** — `toast(...)` enqueues silently and the message never expires (the dismiss timer lives inside the host); `await confirm(...)` never settles, so the caller hangs. Neither throws. - **Two hosts** — every toast and every dialog renders once per host: duplicated DOM, duplicated `role="status"` live regions, duplicated focus traps. `BbToast` warns in DEV; `BbConfirm` does not warn at all. Standalone examples in this repo (`src/components/*/examples/*.vue`, stories, dev showcases) mount a host inline **because each one is a self-contained root with no application around it**. That is a property of the example harness, not a pattern to copy — in a real app the host is already mounted at the root, and the page must not mount a second one. ## Boolean Prop Defaults Rule Boolean props default to `false` so the bare `` shorthand turns the feature **on**. A few props deliberately default to `true` for cross-library keyword clarity — pass `:prop="false"` to opt out: - `multiple` on `BbTable`, `BbCheckboxGroup`, `BbSwitchGroup` — multi-select is the expected default; `:multiple="false"` forces single-select. - `selectable` on the option components (`BbSelect`, `BbSelectPopover`, `BbCheckboxGroup`, `BbRadioGroup`, `BbSwitchGroup`) — options are selectable by default; pass a predicate or `false` to restrict. **Not `BbTable`**: there `selectable` is unset by default and is what renders the selection column at all, so leaving it off means "no checkboxes", not "every row selectable". These are the only intentional default-`true` booleans; anywhere else, `true` default is a bug (`check-api` flags it). **Arrows are tooltip-only in v3.** `hideArrow` / `showArrow` / `arrowPadding` were removed from `BbPopover`, `BbSelectPopover`, `BbColorPalette`, and `BbDropdown` (leftover bindings land silently in `$attrs`). Only `BbTooltip` keeps `hideArrow` (default `false` — arrow shown). See [bb-popover.md](./migration/components/bb-popover.md). ## Testing Against bitboss-ui Two DOM realities of this library trip up headless-browser tests written against otherwise-correct selectors: - **Closed floating panels stay in the DOM, not hidden.** Every floating panel — `BbPopover`, `BbDropdown`, `BbSelectPopover`, and the select family built on it (`BbSelect`) — stays mounted and off-screen when closed (`position: fixed; top: -2000px; width: max-content`) rather than being removed or `display: none`d — a closed panel is parked, not absent. Two consequences: (1) a bare text/role query can match content inside a closed panel elsewhere on the page — scope queries to a known open root; (2) because these panels are `position: fixed` and can be as wide as their longest option, they inflate `document.documentElement.scrollWidth` by however wide the _widest ever rendered_ panel is, with zero visible effect on the page. **Measure page overflow with `document.body.scrollWidth`, never `document.documentElement.scrollWidth`.** **Which closed-state signal to await differs by component.** `BbPopover` and `BbDropdown` wrap their whole panel in `inert` when closed — wait for `.bb-popover__panel[inert]` / `.bb-dropdown__panel[inert]` (not `hidden`/detached) to assert closed, and scope open-panel queries to `:not([inert])`. The select engine (`BbSelectPopover`, and therefore `BbSelect`, which is built on it — the two behave identically) never puts `inert` on its listbox; only its optional header/footer chrome goes `inert`. Its listbox closes via `opacity: 0` + `grid-template-rows: 0fr` on `.bb-listbox__outer-container` (the `bb-listbox--open` class drops). Don't wait for `[inert]` there — it never comes; assert on the collapsed state (`grid-template-rows`/`opacity`/`bb-listbox--open`) or on option visibility instead. **`BbDialog` and `BbOffCanvas` are the trap: they DO carry `inert`, but it does not mean "closed".** Both render `:inert="(!model && !isVisible) || isCovered"`, so a panel is ALSO inert while it is open and merely **covered by another modal stacked on top of it** — assert closed with `[inert]` there and the test starts passing for the wrong reason the moment two overlays stack. They are native `` elements driven by `showModal()`/`close()`, so use the element's own `open` property (or the `bb-dialog--open` class) as the closed-state signal. Mind the transition window too: `inert` only lands once `isVisible` has gone false as well, which is after the closing animation, not at the click. - **Segmented date/time inputs are not one field.** `BbDatePickerInput` / `BbTimePickerInput` render one native input per segment (day/month/year, hour/minute/second/period); the field's own `label` is wired via `for` to only the first segment, and every segment additionally carries its own, localized `aria-label` ("Month"/"Day"/"Year", or "Mese"/"Giorno"/"Anno" under `locale: 'it'`), which wins the accessible-name computation. So `getByLabel('')` matches nothing — query each segment by its own accessible name (`getByLabel('Month')`, etc., which is locale-coupled: it breaks if the app under test switches locale) or by its role/position within the field. `aria-valuetext` is **not** emitted by the date/time picker family — that signal exists only on `BbSelectPopover`'s activator; don't expect it on a segmented input. ## Rebuild Check (before you ship a screen) Re-read what you hand-wrote and ask whether the library already ships it. This is the failure with no error message: the component exists, is exported, is in the picker, and the picker is open — but a picker gets opened to answer _archetype_ questions ("is there a Calendar?"), never "am I about to reinvent something small?". A pilot builder read the picker four times and hand-rolled an avatar twice. Symptoms that mean stop: an `` with an initials/icon fallback → `BbAvatar` (**circle-only** — a rounded-square avatar really is yours to build); a hand-written spin animation → `BbSpinner`; a tinted `` pill standing for a status → `BbBadge` with a registered `soft-*` variant. Full symptom index: [Component Picker](./component-picker.md) § Am I about to rebuild one of ours? `npx bitboss-ui check` prints the avatar and spinner shapes as **advisory hints** — never a build failure, because a round `` is sometimes a logo — so that much backstops itself on files it scans. The badge case is not decidable from markup and has no hint. ## Safety Checklist - [ ] Started from the AI Router (or already knows the next hop). - [ ] Recommends `Bb*` components by default. - [ ] Uses only existing props/events/slots from library types. - [ ] Read the component's "Usage & Guidelines" section before writing usage code. - [ ] Navigation goes through `href`/`to` on buttons and items — no `router.push` click handlers for plain destinations. - [ ] Uses only icons from installed `@iconify-json/*` providers or existing `local:*` files. - [ ] Avoids reference-only imports unless explicitly requested. - [ ] Uses declarative coherence/dependencies before manual watchers. - [ ] Mounts exactly one `` and one `` host, at the app root — never inside a page or component. - [ ] Confirmed the plugin's `locale` is set explicitly if the app isn't Italian (default is `'it'`) — see Setup-First Rule. - [ ] Ran the Rebuild Check over the hand-written markup — nothing reimplements `BbAvatar`, `BbSpinner` or `BbBadge`. --- # Installation and Plugin Setup Source of truth for integrating `bitboss-ui`. Two pieces are always required: 1. a **build plugin** (`bitboss-ui/vite` or `bitboss-ui/nuxt`) that serves the `virtual:bitboss-ui/icons` and `virtual:bitboss-ui/config` modules, scans local SVG icons, and generates typed variant registries; 2. the **runtime plugin** (`bitbossUiPlugin` — installed automatically by the Nuxt module) that injects styles, provides the icon registry, and registers the `v-bb-tooltip`, `v-bb-dropdown`, `v-bb-color`, `v-bb-date` and `v-bb-time` directives. ## 1) Install ```bash # v3 is a prerelease — a dist-tag is REQUIRED. # A bare `npm install bitboss-ui` still resolves to v2, which has no build # plugin and no `dist/ai` knowledge base, so everything below silently fails. npm install bitboss-ui@beta # recommended default icon set (any @iconify-json/* works) npm install -D @iconify-json/lucide ``` Confirm you got v3 before continuing — `npm ls bitboss-ui` must report a `3.0.0-*` version. If it reports `2.x`, nothing in this guide applies. **`@beta` is the current v3 channel.** `@alpha` still resolves, but it is frozen at the last alpha and does not receive fixes — it is not simply an older copy of the same line. `@latest` is the **v2** line and always will be until v3 goes stable, so never reach for it here. Peer dependency: `vue ^3.5.12` — the only required one. `@inertiajs/vue3` is an **optional** peer, needed only in Inertia apps. `@iconify/collections` is an **optional** peer too, and npm never installs optional peers automatically — install it yourself if you want the dev-time provider-icon diagnostics: ```bash npm install -D @iconify/collections ``` Without it the plugin has no list of valid Iconify prefixes, so the dev-overlay errors below (the "install `@iconify-json/`" hint and the "did you mean …?" suggestion for a mistyped icon name) never fire — a wrong provider icon then surfaces only as `BbIcon`'s generic runtime `Icon "…" does not exist.` Nothing else changes: icons that resolve still render, in dev and in production. **Known-good toolchain:** TypeScript **5.x or 6.x** if your build gate runs `vue-tsc`. Both type-check cleanly against the shipped `.d.ts`; the library itself builds on TypeScript 6 with `vue-tsc` 3. TypeScript **7** is the one to avoid for now — it changed its package layout in a way `vue-tsc` (not a peer of this library, but the typical `--noEmit` gate) cannot load, failing with `ERR_PACKAGE_PATH_NOT_EXPORTED` on `./lib/tsc`. This library doesn't pin TypeScript for you (it isn't `vue-tsc`'s maintainer), but a bare `npm i -D typescript` in a fresh project can resolve to 7.x today — pin `typescript: ^5` or `^6` yourself if `vue-tsc` is part of your gate. **`vue-tsc` must be 3.x — 2.x is not "an older version that still works", it is a gate that reports success while checking less than you think.** The library's declarations are emitted by Vue Language Tools 3, and every component declared with `generic="T"` emits as a generic _function_ (`declare const __VLS_export: (__VLS_props …) => VNode`) rather than the `DefineComponent<…>` object non-generic components get. Vue Language Tools 2 does not read props out of that shape, so under `vue-tsc` 2 the template type-checker silently type-checks **nothing** about the props of these nine public components: `BbSelect` · `BbSelectPopover` · `BbTable` · `BbTabs` · `BbTabsRoot` · `BbTree` · `BbRadioGroup` · `BbCheckboxGroup` · `BbSwitchGroup` (and their `BbBase*` primitives). Missing required props, misspelled props, wrong value types — all pass. The trap is that the failure is partial and invisible: unknown-**component** checking still fires, non-generic components like `BbButton` and `BbTextInput` still reject bad props, so the gate is visibly working and you conclude the generic nine are covered too. A real consumer shipped `` (the prop is `legend`) through six release slices on `vue-tsc@^2.2.12` with a green `--noEmit`; the same file under `vue-tsc` 3.2.8 fails immediately with TS2345 + TS2322. There is no runtime symptom to catch it later either — an unknown prop just lands in `$attrs`. Upgrade the gate, don't work around it: ```bash npm i -D vue-tsc@^3 ``` ### Browser support The library targets **evergreen browsers** and ships **no polyfills**. The floor is set by the native [Popover API](https://developer.mozilla.org/docs/Web/API/Popover_API), which every floating surface (selects, dropdowns, popovers, tooltips, color palette) renders through: | Engine | Minimum | | ------------- | ------------------ | | Chrome / Edge | **114** (May 2023) | | Safari | **17** (Sep 2023) | | Firefox | **125** (Apr 2024) | Also relied on, all older than that floor: ``, the `inert` attribute, `ResizeObserver`, and CSS `grid-template-rows` transitions. On older engines the floating surfaces simply don't render — there is no degraded mode — so if your fleet includes browsers below the floor, this library is not a fit without your own polyfill strategy (untested, unsupported). ## 2) Vite + Vue ```ts // vite.config.ts import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; import { bitbossUi } from 'bitboss-ui/vite'; export default defineConfig({ plugins: [ vue(), bitbossUi({ iconDir: './src/assets/icons', }), ], }); ``` ```ts // main.ts import { createApp } from 'vue'; import App from './App.vue'; import { bitbossUiPlugin } from 'bitboss-ui'; const app = createApp(App); app.use(bitbossUiPlugin); app.mount('#app'); ``` If the icon folder does not exist yet, create it (`mkdir -p src/assets/icons`) and keep the path aligned with `iconDir`. ## 3) Nuxt The Nuxt module wraps the same Vite plugin and auto-registers the runtime plugin — no `app.use` needed: ```ts // nuxt.config.ts export default defineNuxtConfig({ modules: ['bitboss-ui/nuxt'], bitboss: { iconDir: './assets/icons', // module default }, }); ``` All options below go under the `bitboss` config key. `nuxtLinkName` controls which globally registered component renders router links (default `'NuxtLink'`). This module supersedes the standalone `@bitboss-dev/bitboss-ui-nuxt` package, which was the v2 Nuxt integration — uninstall it (see [Migration v2 → v3](./migration/v2-to-v3.md)). ### `autoImport?: boolean | { components?: boolean; composables?: boolean; validated?: boolean }` (default: `false`, Nuxt only) Registers the library's components and composables with Nuxt's auto-import system, so templates write `` and `useToast()` with no import line: ```ts // nuxt.config.ts export default defineNuxtConfig({ modules: ['bitboss-ui/nuxt'], bitboss: { iconDir: './assets/icons', autoImport: true, }, }); ``` `true` means `{ components: true, composables: true }`. Pass the object form to narrow it, or to add `validated: true` — which resolves the form controls from [`bitboss-ui/validated`](./validated-forms.md) instead of `bitboss-ui`, so `` is the vee-validate-bound wrapper and takes `rules`, and adds `BbForm`, `BbAsterisk`, `useBbFormContext` and `useValidatedField`. It needs the `vee-validate` peer installed. **It is off by default on purpose, and leaving it off is the better setup.** Explicit imports are what make `vueCompilerOptions.checkUnknownComponents` (see section 9) able to fail the build on a mistyped or unimported `Bb*` tag; with auto-imports on, every `Bb*` tag resolves and that gate goes quiet. Reach for it when you are moving an app off `@bitboss-dev/bitboss-ui-nuxt`, whose templates were written against auto-imported components, and adding the imports is a separate commit from the upgrade. Two things it deliberately does not do. It never overrides your own components: a `~/components/BbButton.vue` in the app keeps winning, and the library only fills names the app has not claimed. And it never registers the library's `useId` — Nuxt already auto-imports Vue's, and shadowing it would silently redirect every existing `useId()` call in the app; import that one explicitly if you want it. ## 4) Inertia Inertia apps are Vite apps: configure `bitboss-ui/vite` exactly as in section 2, install `@inertiajs/vue3`, then install the runtime plugin in `createInertiaApp`: ```ts createInertiaApp({ // ...resolve/setup setup({ el, App, props, plugin }) { createApp({ render: () => h(App, props) }) .use(plugin) .use(bitbossUiPlugin) .mount(el); }, }); ``` That is the whole setup — **you do not need to register a link component**. When the build plugin sees `@inertiajs/vue3` in your app, it imports Inertia's `Link` on your behalf, so `href` on buttons/items performs Inertia visits (`method`, `data`, `headers`, `preserveScroll`, …) out of the box. If your app registers its **own** link wrapper globally — one with baked-in defaults, analytics, whatever — that keeps winning: a component registered under `inertiaLinkName` (default `'Link'`) is always preferred over the imported one. Register it under a different name and point `inertiaLinkName` at it. ## 5) Styling Two runtime options control CSS: - `injectStyles` (default `true`) — auto-injects the library stylesheet (equivalent to `bitboss-ui/styles.css`: base styles + design tokens); - `resetCss` (default `false`) — opt-in injection of the reset stylesheet. Guaranteed order when injected: **reset → library styles → all other stylesheets**, so app-local CSS can always override the library. `resetCss` stays opt-in because Tailwind projects already ship Preflight — a second reset is usually unnecessary. **Tailwind v4 cascade layers vs. scoped ` ``` `CategoryTree` never actually hides. Prefer `v-if`/JS conditionals over utility classes for hide/show behavior on components (yours or ones built on top of `bitboss-ui`) that carry their own layout-affecting scoped styles: ```vue ``` Manual alternatives: ```ts // with injectStyles: false — import once, before your own CSS import 'bitboss-ui/styles.css'; // explicit reset instead of resetCss: true import 'bitboss-ui/reset.css'; ``` **Exception — an app that needs its own CSS to load _before_ the library.** Auto-injection is library-first and not configurable: the runtime plugin inserts its `