Use it for
Reach for BbIcon when the icon is part of the design: a status in a table
cell, empty-state art, a list marker.
Pass Through
Hover or tap a part to outline it. Toggles flip loading, errors and warnings when the component has them — only parts highlight.
Default
Pass the icon name to icon. It is the only required prop.
<template>
<div class="flex items-center gap-4">
<BbIcon icon="lucide:rocket" />
<BbIcon icon="lucide:bell" />
<BbIcon icon="lucide:circle-check" />
</div>
</template>
<script setup lang="ts">
import { BbIcon } from 'bitboss-ui';
</script>
Coming from v2type → icon
The prop was called type in v2. A template still passing type fails at
mount.
Names
icon takes provider:icon, for example lucide:check. For your own SVGs use
local:my-icon (the local: prefix is optional but recommended).
Install the collections you want and the plugin finds them:
npm install -D @iconify-json/lucide
<template>
<div class="flex flex-wrap items-start gap-6">
<!-- A provider set. `lucide` is available because this project has
@iconify-json/lucide installed; nothing else was registered. -->
<figure class="flex flex-col items-center gap-1">
<BbIcon icon="lucide:heart" size="xl" />
<figcaption class="text-xs opacity-70">lucide:heart</figcaption>
</figure>
<!-- The same shape, drawn here: heart.svg inside the configured iconDir
becomes local:heart. -->
<figure class="flex flex-col items-center gap-1">
<BbIcon icon="local:heart" size="xl" />
<figcaption class="text-xs opacity-70">local:heart</figcaption>
</figure>
<!-- Brand art no UI set carries. This is what local: is for. -->
<figure class="flex flex-col items-center gap-1">
<BbIcon icon="local:whatsapp" size="xl" />
<figcaption class="text-xs opacity-70">local:whatsapp</figcaption>
</figure>
<!-- Unprefixed names resolve to local:, never to a provider set. Same
glyph as local:whatsapp — and no editor preview. -->
<figure class="flex flex-col items-center gap-1">
<BbIcon icon="whatsapp" size="xl" />
<figcaption class="text-xs opacity-70">whatsapp</figcaption>
</figure>
</div>
</template>
<script setup lang="ts">
import { BbIcon } from 'bitboss-ui';
</script>
If you write a name that does not exist the app stops at mount, and in
development the overlay tells you which npm install you are missing. The
Iconify extension for VS Code previews icons as you type.
You can also import your own SVGs as strings and pass them straight to icon
(only markup you control: the library does not sanitize it).
<script setup lang="ts">
import mark from '~/assets/marks/partner.svg?raw';
</script>
<template>
<BbIcon :icon="mark" size="2xl" />
</template>
Sizes
Sizes run from xs to 2xl, md (24px) is the default.
<template>
<div class="flex flex-col gap-4">
<!-- The named scale: 12, 16, 24, 28, 36 and 40 px by default. -->
<div class="flex items-end gap-3">
<BbIcon icon="lucide:rocket" size="xs" />
<BbIcon icon="lucide:rocket" size="sm" />
<BbIcon icon="lucide:rocket" />
<BbIcon icon="lucide:rocket" size="lg" />
<BbIcon icon="lucide:rocket" size="xl" />
<BbIcon icon="lucide:rocket" size="2xl" />
</div>
<!-- Off the scale, when a layout needs one exact value. -->
<div class="flex items-end gap-3">
<BbIcon icon="lucide:rocket" :size="20" />
<BbIcon icon="lucide:rocket" size="3rem" />
</div>
</div>
</template>
<script setup lang="ts">
import { BbIcon } from 'bitboss-ui';
</script>
Coming from v2xxl → 2xl
The largest key is now 2xl.
Color
Icons inherit the text color wherever they sit.
- Payment declined
- Shipped this morning
- Not processed yet
<template>
<ul class="flex flex-col gap-2 text-sm">
<!--
The color is on the row, never on the glyph. Each icon paints in
currentColor, so it takes whatever its container is set to — which
is also why the same three icons work unchanged in dark mode.
-->
<li class="flex items-center gap-2 text-[color:var(--bb-danger)]">
<BbIcon icon="lucide:circle-x" size="sm" />
<span>Payment declined</span>
</li>
<li class="flex items-center gap-2 text-[color:var(--bb-primary)]">
<BbIcon icon="lucide:truck" size="sm" />
<span>Shipped this morning</span>
</li>
<li class="flex items-center gap-2 text-[color:var(--bb-text-muted)]">
<BbIcon icon="lucide:circle-dashed" size="sm" />
<span>Not processed yet</span>
</li>
</ul>
</template>
<script setup lang="ts">
import { BbIcon } from 'bitboss-ui';
</script>
Coming from v2color removed
color="#6b7280" becomes a class or a token on the parent. Inside BbButton,
BbBadge or BbAlert you need do nothing: the icon already takes the right
color.
Accessible name
Add label when the icon is the only thing saying something: it gets
role="img" and that text as its name. Leave it off when the text is already
beside it, or screen readers read it twice.
- Two-factor authentication
- Billing
- Card ending 4242
<template>
<dl class="flex max-w-sm flex-col gap-2 text-sm">
<div class="flex items-center justify-between gap-3">
<dt>Two-factor authentication</dt>
<!--
The icon carries the value: nothing else on the row says the
feature is on. `label` gives it role="img" and an accessible
name, so it is announced as "Enabled".
-->
<dd class="text-[color:var(--bb-primary)]">
<BbIcon icon="lucide:shield-check" size="sm" label="Enabled" />
</dd>
</div>
<div class="flex items-center justify-between gap-3">
<dt>Billing</dt>
<!--
Here the text beside the glyph already says everything. No
`label`: the icon is hidden from assistive tech instead of
repeating the sentence next to it.
-->
<dd class="flex items-center gap-1.5">
<BbIcon icon="lucide:credit-card" size="sm" />
<span>Card ending 4242</span>
</dd>
</div>
</dl>
</template>
<script setup lang="ts">
import { BbIcon } from 'bitboss-ui';
</script>
label does not produce a tooltip. Add v-bb-tooltip if you want a visible
one.
When you do not need it
Most components take the icon name on a prop and place it themselves.
Hand-placing a BbIcon inside them is the most common mistake with this
component.
<template>
<!--
Not one BbIcon on this page. Every glyph here arrives as a name on the
host's own prop, and the host places, sizes and colors it to match its
own size — which is the part you would have to rebuild by hand.
-->
<div class="flex max-w-sm flex-col gap-3">
<div class="flex flex-wrap items-center gap-2">
<BbButton variant="primary" prepend:icon="lucide:plus">New order</BbButton>
<BbBadge variant="secondary" prepend:icon="lucide:credit-card">
Paid
</BbBadge>
</div>
<BbTextInput
id="icon-host-search"
v-model="query"
name="search"
label="Search orders"
prepend:icon="lucide:search"
/>
<BbAlert
variant="warning"
icon="lucide:triangle-alert"
text="Two orders are waiting for payment."
hide-close
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { BbAlert, BbBadge, BbButton, BbTextInput } from 'bitboss-ui';
const query = ref('');
</script>
The exceptions are components with no icon field, where you pass a BbIcon in a
slot: BbTabs, BbBreadcrumbs, BbRating, BbSwitch and BbAvatar.
- ORD-2026-0496Pending
- ORD-2026-0474Processing
- ORD-2026-0447Shipped
- ORD-2026-0417Delivered
- ORD-2026-0435Cancelled
- ORD-2026-0421Refunded
<template>
<ul class="flex max-w-sm flex-col gap-2 text-sm">
<li
v-for="order in rows"
:key="order.id"
class="flex items-center gap-2"
:class="STATUS_TONE[order.status]"
>
<BbIcon :icon="STATUS_ICON[order.status]" size="sm" />
<span class="min-w-0 flex-1 truncate font-medium">
{{ order.reference }}
</span>
<span class="shrink-0 text-xs">{{ orderStatusLabels[order.status] }}</span>
</li>
</ul>
</template>
<script setup lang="ts">
import { BbIcon, type IconType } from 'bitboss-ui';
import {
type OrderStatus,
orders,
orderStatusLabels,
orderStatuses,
} from '~/demo-data';
/**
* Every value is a complete literal. The production build scans source files
* for `provider:name` strings and bundles exactly the ones it finds, so
* `lucide:${order.status}` would render in dev and be missing in the build.
*/
const STATUS_ICON: Record<OrderStatus, IconType> = {
pending: 'lucide:clock',
processing: 'lucide:loader',
shipped: 'lucide:truck',
delivered: 'lucide:circle-check',
cancelled: 'lucide:ban',
refunded: 'lucide:rotate-ccw',
};
/** Color rides on the row, so the glyph follows through currentColor. */
const STATUS_TONE: Record<OrderStatus, string> = {
pending: 'text-[color:var(--bb-text-muted)]',
processing: 'text-[color:var(--bb-text-muted)]',
shipped: 'text-[color:var(--bb-primary)]',
delivered: 'text-[color:var(--bb-primary)]',
cancelled: 'text-[color:var(--bb-danger)]',
refunded: 'text-[color:var(--bb-danger)]',
};
/** One order per status, so every literal in the map is on screen. */
const rows = orderStatuses.map(
(status) => orders.find((order) => order.status === status)!
);
</script>