Skip to content

Installation

Install bitboss-ui 3, wire up the two plugins, choose how the styles load, and render your first component.

On this page

v3 is a plugin, not a bag of exports. You always install two pieces. The build plugin generates the config, the icon registry and the typed variant lists. The runtime plugin injects the styles and registers the directives. Without the build plugin the app does not build; without the runtime plugin components render unstyled.

Install the package

bash
npm install bitboss-ui@beta
npm install -D @iconify-json/lucide

Confirm what you got before going further. @latest is the v2 line until v3 goes stable.

bash
# must report 3.0.0-*
npm ls bitboss-ui

Peers. Vue ^3.5.12 is the only required one. The optional ones you may meet: @inertiajs/vue3 in Inertia apps, vee-validate (4.10 or later, below 5) for BbForm, and @modelcontextprotocol/sdk plus zod for the MCP server, which ai-init installs. A build that meets BbForm without vee-validate stops and prints the install command.

Node. 20.19 or later, or 22.12 or later on the 22 line. The bitboss-ui command line needs the same.

Browsers. Evergreen only, with no polyfills. Every floating surface (select, dropdown, popover, tooltip) uses the native Popover API, so the floor is Chrome and Edge 114, Safari 17 and Firefox 125. Below it those surfaces do not render.

Upgrading. Restart the dev server after installing a new version, and delete node_modules/.vite (inside the container under Docker or Sail). A server left running keeps serving the old JavaScript next to the new CSS, which looks like a regression; the console warns when the two disagree. The first start prints the command that lists what broke since your previous version:

bash
npx bitboss-ui changelog --since <previous-version>

The build plugin

Nuxt. The module wraps the Vite plugin and registers the runtime plugin for you, so you write no app.use:

nuxt.config.ts
export default defineNuxtConfig({
    modules: ['bitboss-ui/nuxt'],
});

Vite (Vue, Laravel with Inertia). Add the plugin, then install the runtime plugin yourself in the next section:

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()],
});

You configure everything here, at build time: the Nuxt bitboss key and bitbossUi({ … }) take the same options. Two exceptions: locale can change at runtime through useBbConfig(), and app.use(bitbossUiPlugin, { … }) takes locale and formControls per app.

The library's own strings (empty states, close buttons, pagination) are English unless you set locale. An app in another language names it: bitbossUi({ locale: 'it' }), or bitboss: { locale: 'it' } in Nuxt.

The plugin writes bitboss-ui.d.ts into your project the first time the dev server starts or you build. It lands next to your first script entry (resources/js/ in a Laravel app), else in src/, and in app/ under Nuxt 4. The dts option moves it. The file types your custom variants, enabled locales and passthrough scopes. Commit it, so a teammate's or CI's typecheck agrees without running Vite.

Styles and the runtime plugin

The runtime plugin injects the library stylesheet for you. Do not also import bitboss-ui/styles.css while injection is on. The second copy loads unlayered and beats your Tailwind utilities again. npx bitboss-ui check reports it as hand-imported-styles.

Vite. Install the runtime plugin at the entry point, with no CSS import:

main.ts
import { createApp } from 'vue';
import { bitbossUiPlugin } from 'bitboss-ui';
import App from './App.vue';

createApp(App).use(bitbossUiPlugin).mount('#app');

In an Inertia app the same .use(bitbossUiPlugin) goes in createInertiaApp's setup, after Inertia's own plugin (see Inertia).

Nuxt. The module installs the runtime plugin. The runtime plugin is also what registers v-bb-tooltip, v-bb-dropdown, v-bb-color, v-bb-date, v-bb-time, v-bb-hover-card, v-bb-popover, v-bb-dialog and v-bb-off-canvas; without it those directives throw.

The host. confirm(), toast() and on-demand dialogs render in one <BbHost />, mounted once at the app root beside the router outlet. It takes no props and renders nothing on the server. In Nuxt it goes in app.vue, in Inertia in the render root (see Inertia).

App.vue
<template>
    <RouterView />
    <BbHost />
</template>

<script setup lang="ts">
import { BbHost } from 'bitboss-ui';
</script>

With no host, a confirm() resolves false after a second and toasts wait in a queue. A second host renders nothing. A dev build warns about both.

Reset. The components expect a reset underneath them: sizes are written for border-box, and native controls must arrive without browser chrome. Tailwind's Preflight is one. If you run none, add the library's with resetCss: true. It also sets a system-ui font stack on body at zero specificity, so any font-family of yours wins. The library has no font token and inherits your font, so set font-family on body either way.

Who wins a tie between your CSS and the library depends on how your CSS loads:

  • Plain CSS: leave cssLayers unset. Source order decides: the library loads first, so your CSS wins.
  • Tailwind v3: leave cssLayers unset and turn injection off (below). The source order you set decides.
  • Tailwind v4: set cssLayers to TAILWIND_V4_LAYERS. Your utilities win, always.
  • Other layered CSS: set cssLayers to your full layer order, with 'bitboss-ui' in it. Your layer order decides.

With other layers, put 'bitboss-ui-reset' directly before 'bitboss-ui' when resetCss is on.

Tailwind v4

Tailwind v4 puts its utilities in a cascade layer, and layered CSS loses to the library's unlayered CSS on every property the library sets. px-6 or rounded-lg on a BbButton then does nothing. Set cssLayers to slot the library in below the utilities:

vite.config.ts
import { bitbossUi, TAILWIND_V4_LAYERS } from 'bitboss-ui/vite';

bitbossUi({ cssLayers: TAILWIND_V4_LAYERS });

In Nuxt the same line goes in the bitboss block, with the constant imported from bitboss-ui/nuxt at the top of nuxt.config.ts. You add no @layer line to your own CSS.

One rule stays yours to keep: your own element resets, such as a { color: inherit }, beat the library too when they sit outside a layer. Write them inside @layer base, where the library outranks them.

Tailwind v3

Preflight is ordinary unlayered CSS that loads after the injected library sheet, so its element resets win ties against the components. Turn injection off and import the sheet after your Tailwind entry:

vite.config.ts
bitbossUi({ injectStyles: false });
main.ts
import './app.css';
import 'bitboss-ui/styles.css';

The library now also wins ties against your utilities. To have utilities win on v3, wrap Tailwind's output in layers and list the same layers in cssLayers. Both halves must agree. Injection goes back on, so drop injectStyles: false and the bitboss-ui/styles.css import: the runtime plugin layers the sheet itself.

app.css
@layer tailwind-base, bitboss-ui-reset, bitboss-ui, tailwind-utilities;

@layer tailwind-base {
    @tailwind base;
}

@layer tailwind-utilities {
    @tailwind components;
    @tailwind utilities;
}
vite.config.ts
import { bitbossUi, TAILWIND_V3_LAYERS } from 'bitboss-ui/vite';

// ['tailwind-base', 'bitboss-ui-reset', 'bitboss-ui', 'tailwind-utilities']
bitbossUi({ cssLayers: TAILWIND_V3_LAYERS });

Use the constant rather than retyping the order: a list that drifts from the @layer line loses every tie in silence. In Nuxt, import it from bitboss-ui/nuxt.

On v3, also set passthroughDeduper: false, or point it at a module using tailwind-merge@2: the bundled merger speaks Tailwind v4 class names.

Server rendering

Injection runs in the browser, so a server-rendered page needs the stylesheet from somewhere else, or it shows unstyled components until the JavaScript loads.

Nuxt: nothing to do. The module adds the stylesheet to the server render itself, ahead of your own CSS and layered when cssLayers is set, and turns the browser injection off.

Vite SSR and Inertia SSR with Tailwind v4: import the stylesheet into its layer from your CSS entry, and turn injection off.

resources/css/app.css
@layer theme, base,
    bitboss-ui-reset, bitboss-ui,
    components, utilities;
@import 'tailwindcss';
@import 'bitboss-ui/styles.css' layer(bitboss-ui);
vite.config.ts
bitbossUi({ cssLayers: TAILWIND_V4_LAYERS, injectStyles: false });

The @layer line comes first: it fixes the order before Tailwind declares its own layers, and with injection off nothing else declares the library's.

Nuxt without layers (plain CSS, Tailwind v3): list the stylesheet in css, after your Tailwind entry, and turn injection off: css: ['~/assets/css/main.css', 'bitboss-ui/styles.css'] with bitboss: { injectStyles: false }.

Icons

Icon names are provider-first: lucide:plus resolves from the installed @iconify-json/* package, and local:logo resolves from your own SVG directory.

iconDir defaults to ./assets/icons, resolved from the Vite root. In a Vite app that is the project root, so icons under src/ need iconDir: './src/assets/icons'. In Nuxt 4 the root is app/, so the default already means app/assets/icons. Writing ./app/assets/icons points one level too deep, and every local:* icon then renders as a question mark, in production too.

Drop an SVG in that directory and it is available as local:<filename>. Provider names built at runtime, which the build cannot see as literals, go in iconWhitelist. Icons has the rest.

Options most apps set

The same options go in bitbossUi({ … }) and in Nuxt's bitboss key. The ones most apps touch, by what they control:

Language and icons:

  • locale: the language of the library's own strings, and the formats around them. Default 'en'. It is the one option you can change at runtime. See Locales.
  • enabledLocales: which of the 38 built-in locales enter the bundle. Default: your locale plus 'en'.
  • iconDir: the folder of SVG files for local:* icons, from the Vite root. Default './assets/icons'.
  • iconWhitelist: provider icons that always ship, for names built at runtime, such as 'lucide:flag' or 'tabler:calendar-*'.

Styles:

  • cssLayers: places the library in your cascade layer order, TAILWIND_V4_LAYERS for Tailwind v4. Unset by default.
  • injectStyles: injects the stylesheet in the browser. Default true; set it false when you import the stylesheet yourself.
  • resetCss: adds the library reset, system-ui body font included. Default false.

Forms and selects:

  • formControls: what an emptied BbTextInput or BbTextarea emits, and when fields in a BbForm validate. Default textInputNullValue: 'null' ('string' emits ''), validationMode: 'balanced'.
  • requiredAsterisk: a red * beside the label of required fields. Outside a form that is a field with required; in a BbForm it is a field whose rules include required or whose schema key is required, or that carries the required hint. Default true; false hides the marker everywhere.
  • defaultInputLabelMode: where field labels sit, 'outside' (the default), 'floating' or 'inside'.
  • autoDisableWritingThreshold: a select with fewer options than this opens with no search field. Default 6; 0 keeps the search field on every select.

Every instance of a component:

  • pt: the classes and attributes each instance starts from, keyed by component and part. See Passthrough.
  • toastPosition: the corner toasts stack in, unless a toast names its own. Default 'bottom-right'.
  • confirm: { defaultSize }, the maximum width of every confirm, from 'xs' to '2xl', unless a call passes its own size. Unset leaves 'sm'.

Phones and the server render:

  • adaptive: dialogs, popovers, dropdowns and selects open as bottom sheets below mobileMaxWidth. Default true.
  • mobileMaxWidth: the width in pixels below which the mobile layouts apply. Default 768.
  • ssrViewportWidth: the screen width the server renders for, since it cannot measure one. Default 1024.

The dev server:

  • mcp: registers the MCP server in the project's agent configs when dev starts. Default false.
  • vscodeSettings: writes editor hints to .vscode/settings.json when dev starts. Default true.

The options are typed, so your editor lists the rest. They are the variant registries (see Design language), injectDirectives, the size scales, dts, iconsScanner, passthroughDeduper and the link settings (inertiaLinkName, nuxtLinkName, linkActiveClass). The complete reference ships in the package, at node_modules/bitboss-ui/dist/ai/guides/installation-and-plugin-setup.md.

formControls and confirm can also be set per app, merged key by key, with app.use(bitbossUiPlugin, { formControls: { textInputNullValue: 'string' } }).

Locales

locale sets more than the words. It sets the formats around them: the long date, the first day of the week, the order of a date field's parts, the AM / PM words, and how a number field groups what the user types.

The list is fixed and typed. A bare language is its default region: en is en-US, it is it-IT, pt is pt-BR and zh is zh-CN.

LanguageDefaultRegions, same wordsOwn dictionary
Englishen (US)en-GB, en-AU, en-CA, en-IE
Germandede-ATde-CH
Frenchfrfr-BE, fr-CA, fr-CH
Italianitit-CH
Dutchnlnl-BE
Swedishsvsv-FI
Spanishes (Spain)es-MX (Mexico)
Portuguesept-BR (Brazil)pt-PT (Portugal)
Chinesezh-CN (Simplified)zh-TW (Taiwan), zh-HK (Hong Kong): Traditional
One region eachcs da el fi hu ja ko nb pl ro ru tr uk

A region reads its language's words and keeps its own formats: de-CH groups 1’234.5, en-GB starts the week on Monday and orders dates DD/MM/YYYY, en-CA starts it on Sunday, and fr-CA orders them AAAA-MM-JJ. The English regions spell the British way (colour), and de-CH writes ss for ß. Where the words themselves differ, the region has a dictionary of its own: Mexican Spanish, European Portuguese (its week starts on Sunday, as CLDR has it), and Traditional Chinese for Taiwan and Hong Kong.

vite.config.ts
bitbossUi({
    locale: 'en-GB',
    enabledLocales: ['fr-CA', 'de-CH'],
});
  • pt-BR and zh-CN are the canonical spellings. Codes are case-insensitive, so zh-cn still works, and a default region's full code is the same locale (it-IT is it).
  • A code that is not on the list is a type error. In a component, npx bitboss-ui check also flags it and names the nearest one: for setConfig({ locale: 'en-UK' }) it asks whether you meant en-GB.
  • Selecting a locale you did not enable is a compile error, not a runtime fallback. enabledLocales decides what ships; at runtime useBbConfig().setConfig({ locale }) switches among those only. There is deliberately no 'all': each dictionary is a bundle cost you should see in the config.
  • There is no way to add your own. A language or region that is missing is a request for the library, not a dictionary in your app.

Imports and the type gate

Import components explicitly:

vue
<script setup lang="ts">
import { BbButton, BbSelect } from 'bitboss-ui';
</script>

The Nuxt module can auto-import them instead, with autoImport: true in the bitboss block. Leave it off, which is the default. Explicit imports are what let the next setting catch a mistyped or removed component at type-check time:

tsconfig.json
{
    "vueCompilerOptions": {
        "checkUnknownComponents": true,
        "strictTemplates": true
    }
}

strictTemplates does the same for a misspelled prop. Run both through vue-tsc 3. Under 2.x, the props of the generic components (BbSelect, BbSelectPopover, BbTable, BbTabs, BbTabsRoot, BbTree, BbForm and the checkbox, radio and switch groups) go unchecked while everything else still errors, so the gate looks healthy. Keep TypeScript at 5.x or 6.x: vue-tsc cannot load TypeScript 7 yet.

With auto-imports on, every Bb* tag resolves, so this check and the bitboss-ui/no-unimported-component ESLint rule both go quiet.

Your first component

A select of team members, from the objects your API returns:

Luca Ferrari

Stored value: 27

label is required: it is the accessible name. Pass your domain objects to items as they are, then name the display field (item-text) and the stored value (item-value). You never map them to { label, value } pairs first. Objects that already carry label and value need neither prop.

Where to go next

Choose the next page by task: