Examples
Basic usage
Overview
brio is a small CSS component library for internal use, with a JS layer of progressive-enhancement custom elements where the platform doesn't go far enough.
Install
Clone the repo, run the build, link the bundled CSS and JS. Detailed steps in the README.
Changelog
v0.2 in progress: popover, dropdown, range, tabs, tooltip, plus an extras layer.
<brio-tabs class="tabs">
<div role="tablist">
<button
class="tab"
type="button"
role="tab"
aria-selected="true"
aria-controls="ex-overview"
id="ex-tab-overview"
>
Overview
</button>
<button
class="tab"
type="button"
role="tab"
aria-controls="ex-install"
id="ex-tab-install"
tabindex="-1"
>
Install
</button>
<button
class="tab"
type="button"
role="tab"
aria-controls="ex-changelog"
id="ex-tab-changelog"
tabindex="-1"
>
Changelog
</button>
</div>
<div
class="tab-panel"
role="tabpanel"
id="ex-overview"
aria-labelledby="ex-tab-overview"
>
<h3>Overview</h3>
<p>
brio is a small CSS component library for internal use, with a JS layer of
progressive-enhancement custom elements where the platform doesn't go far
enough.
</p>
</div>
<div
class="tab-panel"
role="tabpanel"
id="ex-install"
aria-labelledby="ex-tab-install"
hidden
>
<h3>Install</h3>
<p>
Clone the repo, run the build, link the bundled CSS and JS. Detailed steps
in the README.
</p>
</div>
<div
class="tab-panel"
role="tabpanel"
id="ex-changelog"
aria-labelledby="ex-tab-changelog"
hidden
>
<h3>Changelog</h3>
<p>
v0.2 in progress: popover, dropdown, range, tabs, tooltip, plus an extras
layer.
</p>
</div>
</brio-tabs>
Pills
Today's activity, hour by hour.
This week's activity, day by day.
This month's activity, week by week.
<brio-tabs class="tabs is-pills">
<div role="tablist">
<button
class="tab"
type="button"
role="tab"
aria-selected="true"
aria-controls="pills-day"
id="pills-tab-day"
>
Day
</button>
<button
class="tab"
type="button"
role="tab"
aria-controls="pills-week"
id="pills-tab-week"
tabindex="-1"
>
Week
</button>
<button
class="tab"
type="button"
role="tab"
aria-controls="pills-month"
id="pills-tab-month"
tabindex="-1"
>
Month
</button>
</div>
<div
class="tab-panel"
role="tabpanel"
id="pills-day"
aria-labelledby="pills-tab-day"
>
<p>Today's activity, hour by hour.</p>
</div>
<div
class="tab-panel"
role="tabpanel"
id="pills-week"
aria-labelledby="pills-tab-week"
hidden
>
<p>This week's activity, day by day.</p>
</div>
<div
class="tab-panel"
role="tabpanel"
id="pills-month"
aria-labelledby="pills-tab-month"
hidden
>
<p>This month's activity, week by week.</p>
</div>
</brio-tabs>
Vertical
Profile
Display name, avatar, contact details.
Billing
Current plan, payment method, invoice history.
Team
Members, roles, invitations.
API keys
Create, revoke, and rotate access tokens.
<brio-tabs class="tabs is-vertical">
<div role="tablist">
<button
class="tab"
type="button"
role="tab"
aria-selected="true"
aria-controls="vert-profile"
id="vert-tab-profile"
>
Profile
</button>
<button
class="tab"
type="button"
role="tab"
aria-controls="vert-billing"
id="vert-tab-billing"
tabindex="-1"
>
Billing
</button>
<button
class="tab"
type="button"
role="tab"
aria-controls="vert-team"
id="vert-tab-team"
tabindex="-1"
>
Team
</button>
<button
class="tab"
type="button"
role="tab"
aria-controls="vert-api"
id="vert-tab-api"
tabindex="-1"
>
API keys
</button>
</div>
<div
class="tab-panel"
role="tabpanel"
id="vert-profile"
aria-labelledby="vert-tab-profile"
>
<h3>Profile</h3>
<p>Display name, avatar, contact details.</p>
</div>
<div
class="tab-panel"
role="tabpanel"
id="vert-billing"
aria-labelledby="vert-tab-billing"
hidden
>
<h3>Billing</h3>
<p>Current plan, payment method, invoice history.</p>
</div>
<div
class="tab-panel"
role="tabpanel"
id="vert-team"
aria-labelledby="vert-tab-team"
hidden
>
<h3>Team</h3>
<p>Members, roles, invitations.</p>
</div>
<div
class="tab-panel"
role="tabpanel"
id="vert-api"
aria-labelledby="vert-tab-api"
hidden
>
<h3>API keys</h3>
<p>Create, revoke, and rotate access tokens.</p>
</div>
</brio-tabs>
Stretched
Pay month to month, cancel anytime.
One yearly payment, two months free.
<brio-tabs class="tabs is-stretched">
<div role="tablist">
<button
class="tab"
type="button"
role="tab"
aria-selected="true"
aria-controls="str-monthly"
id="str-tab-monthly"
>
Monthly
</button>
<button
class="tab"
type="button"
role="tab"
aria-controls="str-yearly"
id="str-tab-yearly"
tabindex="-1"
>
Yearly (save 20%)
</button>
</div>
<div
class="tab-panel"
role="tabpanel"
id="str-monthly"
aria-labelledby="str-tab-monthly"
>
<p>Pay month to month, cancel anytime.</p>
</div>
<div
class="tab-panel"
role="tabpanel"
id="str-yearly"
aria-labelledby="str-tab-yearly"
hidden
>
<p>One yearly payment, two months free.</p>
</div>
</brio-tabs>
Activation modes
Manual activation: focus the tablist with Tab, walk between tabs with arrow keys, then press Enter or Space to switch panels.
Use this mode when panel switches are expensive (network, heavy compute) so keyboard users aren't firing N requests as they navigate.
Mouse click still activates immediately in either mode -- only keyboard arrow-key behavior differs.
<brio-tabs class="tabs" manual>
<div role="tablist">
<button
class="tab"
type="button"
role="tab"
aria-selected="true"
aria-controls="man-a"
id="man-tab-a"
>
Tab A
</button>
<button
class="tab"
type="button"
role="tab"
aria-controls="man-b"
id="man-tab-b"
tabindex="-1"
>
Tab B
</button>
<button
class="tab"
type="button"
role="tab"
aria-controls="man-c"
id="man-tab-c"
tabindex="-1"
>
Tab C
</button>
</div>
<div class="tab-panel" role="tabpanel" id="man-a" aria-labelledby="man-tab-a">
<p>
Manual activation: focus the tablist with Tab, walk between tabs with arrow
keys, then press Enter or Space to switch panels.
</p>
</div>
<div
class="tab-panel"
role="tabpanel"
id="man-b"
aria-labelledby="man-tab-b"
hidden
>
<p>
Use this mode when panel switches are expensive (network, heavy compute) so
keyboard users aren't firing N requests as they navigate.
</p>
</div>
<div
class="tab-panel"
role="tabpanel"
id="man-c"
aria-labelledby="man-tab-c"
hidden
>
<p>
Mouse click still activates immediately in either mode -- only keyboard
arrow-key behavior differs.
</p>
</div>
</brio-tabs>
Source comments
Tabs -- ARIA-tabbed content with a tablist + tabpanels structure.
Activation mode (JS-only behavior, on the custom element):
<brio-tabs manual> Arrow keys move focus only; Enter / Space
activates. Use when activation has side effects
(network requests, heavy compute) that shouldn't
fire on every arrow press.
default Arrow keys move focus AND activate -- the
conventional ARIA tab pattern.
Item state (per P5 -- ARIA over .is-* for interactive state):
:hover, :focus-visible
[aria-selected="true"] Active tab
:disabled, [aria-disabled="true"] Disabled tab (skipped by
keyboard nav, no click)
tabindex="-1" Inactive tab (roving tabindex
managed by brio-tabs)
Markup:
<brio-tabs class="tabs">
<div role="tablist">
<button class="tab" type="button" role="tab"
aria-selected="true" aria-controls="p1" id="t1">One</button>
<button class="tab" type="button" role="tab"
aria-controls="p2" id="t2" tabindex="-1">Two</button>
</div>
<div class="tab-panel" role="tabpanel" id="p1" aria-labelledby="t1">...</div>
<div class="tab-panel" role="tabpanel" id="p2" aria-labelledby="t2" hidden>...</div>
</brio-tabs>
Archetype: ARIA-first subtype of Slotted container (P3). State lives in
ARIA attributes; CSS selectors bind to those attributes; a custom
element handles the keyboard / focus orchestration. The .tab and
.tab-panel classes are styling-equivalent to their ARIA roles, paired
the same way .dropdown-item is paired with [role="menuitem"] -- the
role is the API, the class is a parallel hook.
Reference
Source: src/css/tabs.css
Applies to
.tabs
Classes
| Class | Kind | Description |
|---|---|---|
.is-vertical | variant | Side-tab orientation with column tablist |
.is-stretched | variant | Tabs grow equally to fill the tablist width |
.is-pills | variant | Pill-shaped triggers with filled active state |
Slots
| Slot | Description |
|---|---|
.tabs [role="tablist"] | Tablist -- styled via the role since it's a structural container, * not a separately-named slot. Inset shadow is a deliberate choice: * the active tab's bottom-border indicator overlays cleanly without * a negative margin, which would get clipped by overflow-x scrolling. |
.tabs :is([role="tab"], .tab) | Tab -- both .tab class and [role="tab"] are styling hooks. Same * dual-pairing pattern as .dropdown-item + [role="menuitem"]. |
Tokens
| Token | Default | Description |
|---|---|---|
--tabs-font-size | var(--ui-font-size-sm) | Font size for tab triggers |
--tabs-padding-inline | var(--ui-pad-inline-compact) | Inline-axis padding for tab triggers |
--tabs-padding-block | var(--ui-pad-block-tight) | Block-axis padding for tab triggers |
--tabs-gap | var(--ui-gutter-tight) | Gap between tab triggers |
--tabs-color | var(--color-text-muted) | Text color for inactive tabs |
--tabs-color-active | var(--color-primary) | Text color for the active tab |
--tabs-border-color | var(--color-border) | Border color for the tablist underline |
--tabs-border-color-active | var(--color-primary) | Border color for the active tab indicator |
--tabs-panel-padding-block | var(--space-md) | Block-axis padding for tab panels |
--tabs-vertical-min-width | 10rem | Minimum inline size of the vertical tablist |