Skip to content

BbBaseButton

Build custom action and navigation primitives on a native button, anchor, or router link.

import { BbBaseButton } from 'bitboss-ui';

On this page

Use it for

Use BbBaseButton when your design is not a button variant: a card overlay, list row, inline link or custom navigation item. Omit a destination for an action; pass href or to for navigation.

Use something else when

  • BbButton, if you want something that looks like a button, including a quiet one: that is variant="ghost"

Pass Through

Hover or tap a part to outline it. Toggles flip loading, errors and warnings when the component has them — only parts highlight.

BbButton and other components are built on this.

No chrome, your class

No styles included: padding, background, border and colour come from your own classes. The one thing to add is a visible focus style: bb-base-button ships the box-shadow transition, not the ring.

The label is the default slot. text is a string-only fallback for loops that spread a config object.

What it renders

The props pick the tag, in this order:

  1. A router link: to is set, or an Inertia app gets a same-origin href with no target. Which component (RouterLink, NuxtLink, Inertia's Link) depends on the framework.
  2. An anchor: any other href: external URLs, anything with target, download or external, and a disabled to link.
  3. A native <button>: no destination.

<button> — no destination, so an action

Jump to navigation <a href> — middle-click and copy-address work

bitboss-ui on npm <a target="_blank" rel="noopener noreferrer">

Any target other than _self gets rel="noopener noreferrer", named windows included.

If a click goes somewhere, put that destination on href or to. An @click that calls router.push cannot open in a new tab and is not a link to assistive technology.

Which prop you use depends on the framework:

vue
<!-- Vue Router or Nuxt: `to` keeps the navigation inside the app. -->
<BbBaseButton :to="{ name: 'orders.show', params: { id: order.id } }">
    {{ order.reference }}
</BbBaseButton>

<!-- The same destination as an href (`/orders`) is a real document
     navigation in that app: a full page load. -->
<BbBaseButton :href="ordersPath">All orders</BbBaseButton>

Under Inertia the rule flips: a same-origin href without a target becomes an Inertia visit. Configure the visit with props, not listeners: request shape (method, data, headers, only, except), history and scroll (replace, preserve-scroll, preserve-state), lifecycle hooks (on-before, on-start, on-progress, on-success, on-error, on-finish).

vue
<!-- Inertia: the href (`/invoices`) is the visit; everything else rides
     along as props. -->
<BbBaseButton
    :data="{ customerId }"
    :href="invoicesPath"
    method="post"
    :on-success="() => drawer.close()"
    preserve-scroll
>
    Create invoice
</BbBaseButton>

Three props force a plain anchor and skip routing: target, download (including blob: and data: URLs) and external. replace swaps the history entry instead of pushing one, under both Vue Router and Inertia. Without a router, only href works.

The link that points here

When the link points at the current location the component adds a class and, on an exact match, aria-current (page unless aria-current-value says otherwise). It applies no styling: you write the highlight against the class you named.

vue
<BbBaseButton
    class="nav-item"
    exact-active-class="nav-item--current"
    :to="{ name: 'settings.members' }"
>
    Members
</BbBaseButton>

active-class matches a path prefix (a section link that stays lit on child pages). exact-active-class only matches exactly. Without them you get the defaults, router-link-active and router-link-exact-active, or whatever linkActiveClass / linkExactActiveClass set on the plugin. A fragment-only href (#section) is the page you are already on, so it never matches.

A whole region as one link

A simple row can be the link itself. For a richer card, keep the content in a plain container and stretch one BbBaseButton overlay across it. The overlay gets a short accessible name while badges, metadata and secondary controls stay outside the link.

Keep the overlay's label specific: “Open Acme workspace” is better than making a screen reader announce every value in the card. Put secondary controls above the overlay and keep them as separate interactive elements.

A record identity in a table cell, a reference in a sentence, a value in a property grid: links without a button box.

Refund issued against ORD-2026-0417 on 4 June, after the customer reported a damaged desk.

BbBaseButton + your own .plain-link — the link is part of the sentence, wraps with it, and inherits its colour.

Refund issued against ORD-2026-0417 on 4 June, after the customer reported a damaged desk.

BbButton variant="link" — still a button box: its own height and padding push the line apart.

BbButton's variant="link" changes the colour but keeps the button's height and padding, so it cannot flow with the text or truncate at cell width. On BbBaseButton use a project class (for example .plain-link), defined once and reused on identity cells.

Disabled adapts to the element

One prop, two behaviours: the platform has no disabled <a>.

Open the archived invoice

Neither one is reachable by keyboard or pointer.

On a button it sets the native disabled attribute. On a link it drops the href and sets role="link", aria-disabled="true" and tabindex="-1": unfollowable and unfocusable, still in the layout. Both get bb-base-button--disabled, so one rule styles either. Prefer that to a dead href that 404s.

Inside a form, and full width

type defaults to button, so nothing submits by accident. The one that should submit says so.

Press Enter in the field, or use the button.

block adds bb-base-button--block for a full-width control: stacked nav items, list rows, a mobile call to action. tag renders the non-link case as a div, span or label (for example a <label> wrapping a hidden file input, or where a nested <button> would be invalid markup). You lose the native type and disabled attributes and keyboard activation: use it only when you have no alternative, then restore the missing focus and keyboard behaviour.