Tab
<loomi-tabs> builds a heading bar from its <loomi-tab> children and toggles which
panel is visible. There is no separate heading/body/content wiring to keep in sync — each <loomi-tab> carries its own
heading (label/icon) and its own panel content together, so there’s nothing to
name-match by hand.
npm install @loomidev/tab litimport "@loomidev/tab";Basic Usage
Section titled “Basic Usage”Wrap any number of <loomi-tab> elements in a <loomi-tabs>. The tab marked active
is selected by default — it doesn’t have to be the first one. If none is marked
active, the first non-disabled tab is selected automatically.
Account profile
Update the public name, team role, and contact email shown across the workspace.
Security
Require two-step verification and review the devices that are currently signed in.
Notifications
Choose which product updates, approvals, and billing alerts should send email.
<loomi-tabs> <loomi-tab label="Profile" active> <h3>Account profile</h3> <p>Update the public name, team role, and contact email shown across the workspace.</p> <loomi-button size="small">Save profile</loomi-button> </loomi-tab> <loomi-tab label="Security"> <h3>Security</h3> <p>Require two-step verification and review the devices that are currently signed in.</p> <loomi-button size="small" type="secondary">Manage devices</loomi-button> </loomi-tab> <loomi-tab label="Notifications"> <h3>Notifications</h3> <p>Choose which product updates, approvals, and billing alerts should send email.</p> <loomi-toggle label="Weekly summary" checked></loomi-toggle> </loomi-tab></loomi-tabs>Listen for loomi-tab-change on <loomi-tabs> if you need to react to the switch (e.g. lazy-
loading a panel’s data):
document.querySelector("loomi-tabs").addEventListener("loomi-tab-change", (e) => { console.log(e.detail.label); // the newly active tab's label});Different Colors
Section titled “Different Colors”The active tab’s underline (or background, depending on style — see below) uses the
default. Set color on <loomi-tabs> to pick a different one; it applies to every
child tab.
2 open incidents are affecting checkout. The team was paged 6 minutes ago.
14 incidents were resolved this week, with a median time-to-fix of 22 minutes.
All 312 checks are passing on the main branch as of the last deploy.
4 checks are skipped for this branch because they only run on release tags.
<loomi-tabs color="error"> <loomi-tab label="Incidents" active> <p>2 open incidents are affecting checkout. The team was paged 6 minutes ago.</p> </loomi-tab> <loomi-tab label="Resolved"> <p>14 incidents were resolved this week, with a median time-to-fix of 22 minutes.</p> </loomi-tab></loomi-tabs>
<loomi-tabs color="success"> <loomi-tab label="Passing" active> <p>All 312 checks are passing on the main branch as of the last deploy.</p> </loomi-tab> <loomi-tab label="Skipped"> <p>4 checks are skipped for this branch because they only run on release tags.</p> </loomi-tab></loomi-tabs>Common semantic colors: primary secondary info success error warning gray.
Other Tab Styles
Section titled “Other Tab Styles”<loomi-tabs> comes in three styles, set via tab-style. The default is simple (an
underlined heading row, as in every example above).
When the active tab changes, the heading indicator slides to the next tab. In simple
style the underline moves; in system and pills styles the selected surface moves.
System Tab Style
Section titled “System Tab Style”A segmented-control look — the active tab gets a raised pill inside a tinted track.
<loomi-tabs tab-style="system"> <loomi-tab label="Monthly" active>…</loomi-tab> <loomi-tab label="Yearly">…</loomi-tab></loomi-tabs>Pills Tab Style
Section titled “Pills Tab Style”The same segmented-control look as system, with fully rounded tab buttons.
<loomi-tabs tab-style="pills"> <loomi-tab label="All" active>24 open conversations</loomi-tab> <loomi-tab label="Unread">5 conversations need a reply</loomi-tab> <loomi-tab label="Archived">128 resolved conversations</loomi-tab></loomi-tabs>With Icons
Section titled “With Icons”Set icon on a <loomi-tab> to prefix its heading with an icon from the shared
@loomidev/icons registry. Works in any of the three styles.
Your workspace is on the Team plan with 8 of 10 seats in use.
Ada invited a new member and Sara updated the billing contact today.
Two-step verification is required for all admins and enabled for 6 of 8 members.
<loomi-tabs> <loomi-tab label="Overview" icon="information-circle" active> <p>Your workspace is on the Team plan with 8 of 10 seats in use.</p> </loomi-tab> <loomi-tab label="Activity" icon="bell-alert"> <p>Ada invited a new member and Sara updated the billing contact today.</p> </loomi-tab> <loomi-tab label="Security" icon="lock-closed"> <p>Two-step verification is required for all admins and enabled for 6 of 8 members.</p> </loomi-tab></loomi-tabs>Need an icon that isn’t built in? Register your own — no need to fork the registry:
import { registerLoomiIcon } from "@loomidev/icons";import { svg } from "lit";
registerLoomiIcon("rocket", svg`<path d="…" />`);<loomi-tab label="Launches" icon="rocket">…</loomi-tab>Disabled Tabs & Tabs That Navigate
Section titled “Disabled Tabs & Tabs That Navigate”Set disabled to fade out a tab and ignore clicks on it (and skip it during
keyboard navigation — see below):
Arrives in 5-7 business days. Free on orders over $50.
Not available for this address yet.
<loomi-tabs> <loomi-tab label="Standard Shipping" active> <p>Arrives in 5-7 business days. Free on orders over $50.</p> </loomi-tab> <loomi-tab label="Same-Day Delivery" disabled> <p>Not available for this address yet.</p> </loomi-tab></loomi-tabs>Set url instead of relying on the built-in panel switching to make a tab behave like a
plain link — clicking it navigates via location.href rather than showing a panel:
Quick glance: 1,284 active users, 42 open invites, $12.8k in monthly spend.
<loomi-tabs> <loomi-tab label="Dashboard" active> <p>Quick glance: 1,284 active users, 42 open invites, $12.8k in monthly spend.</p> </loomi-tab> <loomi-tab label="Full Settings →" url="/settings"></loomi-tab></loomi-tabs>Keyboard Navigation
Section titled “Keyboard Navigation”<loomi-tabs> implements the WAI-ARIA APG “tabs” pattern
out of the box: once a tab heading has focus, ←/→ (or
↑/↓) move between tabs and switch the active panel immediately
(automatic activation), and Home/End jump to the first/last
enabled tab. Disabled tabs are skipped. No setup required — this works the same in
every style.
Accessibility
Section titled “Accessibility”- See APG link in README.
For the library-wide baseline, see Foundations — Accessibility.
Responsive behavior
Section titled “Responsive behavior”For the shared container and viewport rules, see Foundations — Responsive behavior.
Dark mode
Section titled “Dark mode”For theme activation, token overrides, and contrast guidance, see Foundations — Dark mode.
Attributes
Section titled “Attributes”<loomi-tabs>
Section titled “<loomi-tabs>”| Attribute | Default | Description |
|---|---|---|
color | primary | Active-tab color. Any loomi color. |
tab-style | simple | simple | system | pills |
<loomi-tab>
Section titled “<loomi-tab>”| Attribute | Default | Description |
|---|---|---|
label | (blank) | Heading text. |
icon | (blank) | Heading icon name (see @loomidev/icons). |
active | false | Selected by default. (boolean) |
disabled | false | Disabled tab — faded out, ignores clicks and keyboard focus. (boolean) |
url | (blank) | Navigate to this URL instead of switching panels. |
| Slot | Description |
|---|---|
| (default) | Content placed inside the component. |
Events
Section titled “Events”| Event | Description |
|---|---|
loomi-tab-change | Fired when the tab changes. |
Full Example
Section titled “Full Example”Revenue is up 12% this month and all scheduled payouts have cleared.
Three payment disputes need a response before Friday.
Nothing archived yet.
<loomi-tabs color="success" tab-style="pills"> <loomi-tab label="Overview" icon="information-circle" active> <p>Revenue is up 12% this month and all scheduled payouts have cleared.</p> </loomi-tab> <loomi-tab label="Activity" icon="bell-alert"> <p>Three payment disputes need a response before Friday.</p> </loomi-tab> <loomi-tab label="Archived" disabled> <p>Nothing archived yet.</p> </loomi-tab> <loomi-tab label="Full report →" url="/reports/full"></loomi-tab></loomi-tabs>Framework integration
Section titled “Framework integration”<loomi-tab> and <loomi-tabs> are standard custom elements, so the browser can use them in plain HTML, Blade, React, Vue, Angular, Svelte, Astro, and most other frameworks. The important beginner rule is: install the package, import it once before the tag is rendered, then write the Loomi tag in your template.
Where to run commands
Section titled “Where to run commands”Run install commands from the app where you want to use this component. That means the folder that contains that app’s package.json. Do not run these install commands from packages/tab unless you are editing LoomiUI itself.
cd /path/to/your-appnpm install @loomidev/tab litIf you are contributing to LoomiUI itself, first move to the top-level components folder. That is where the main package.json for all packages lives, and pnpm --filter ... commands should be run from there:
cd /path/to/your-copy-of-loomiui/componentspnpm --filter @loomidev/tab buildpnpm --filter @loomidev/tab typecheckChoose your framework
Section titled “Choose your framework”Use the CDN version for prototypes, documentation pages, or a quick reproduction. The import map tells the browser where to find Lit, which Loomi components use internally.
<script type="importmap"> { "imports": { "lit": "https://esm.sh/lit@3.3.3", "lit/": "https://esm.sh/lit@3.3.3/" } }</script><script type="module" src="https://esm.sh/@loomidev/tab"></script>
<loomi-tabs> <loomi-tab label="Overview" active>Account summary</loomi-tab> <loomi-tab label="Invoices">Recent invoices</loomi-tab></loomi-tabs>In Vite, Webpack, Parcel, Rollup, or a framework build pipeline, install the package and import it once in your main app JavaScript file. After that, you can use the Loomi tag anywhere in your app.
import "@loomidev/tab";Run the install command from your Laravel project root, then import the component in resources/js/app.js. If your project uses Laravel Vite, npm run dev and npm run build should also be run from the Laravel project root.
cd /path/to/your-laravel-appnpm install @loomidev/tab litnpm run devimport "@loomidev/tab";<loomi-tabs> <loomi-tab label="Overview" active>Account summary</loomi-tab> <loomi-tab label="Invoices">Recent invoices</loomi-tab></loomi-tabs>React can render Loomi tags directly. If you are on React 18, or if you need to pass arrays, objects, or functions, use a ref and assign those values after the component mounts.
import "@loomidev/tab";
export function LoomiExample() { return ( <loomi-tabs> <loomi-tab label="Overview" active>Account summary</loomi-tab> <loomi-tab label="Invoices">Recent invoices</loomi-tab> </loomi-tabs> );}If TypeScript does not recognize the Loomi tag in JSX, add it to your app’s JSX type declarations.
Import the package in the component that uses it, or once in your main Vue file. Vue templates can use Loomi tags directly. For arrays, objects, or functions, pass the value as a JavaScript property instead of as plain text.
<script setup>import "@loomidev/tab";</script>
<template> <loomi-tabs> <loomi-tab label="Overview" active>Account summary</loomi-tab> <loomi-tab label="Invoices">Recent invoices</loomi-tab> </loomi-tabs></template>If Vue warns that the tag is an unknown component, configure compilerOptions.isCustomElement for tags that start with loomi- in your Vite or Vue config.
Import the package once and tell Angular to allow custom HTML tags with CUSTOM_ELEMENTS_SCHEMA. For NgModule apps, add the schema to the module instead of the standalone component.
import { CUSTOM_ELEMENTS_SCHEMA, Component } from "@angular/core";import "@loomidev/tab";
@Component({ selector: "app-root", standalone: true, schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <loomi-tabs> <loomi-tab label="Overview" active>Account summary</loomi-tab> <loomi-tab label="Invoices">Recent invoices</loomi-tab> </loomi-tabs> `,})export class AppComponent {}Svelte can import the package inside a component script. Astro can import it in the frontmatter of the page or layout where the tag appears.
<script> import "@loomidev/tab";</script>
<loomi-tabs> <loomi-tab label="Overview" active>Account summary</loomi-tab> <loomi-tab label="Invoices">Recent invoices</loomi-tab></loomi-tabs>---import "@loomidev/tab";---
<loomi-tabs> <loomi-tab label="Overview" active>Account summary</loomi-tab> <loomi-tab label="Invoices">Recent invoices</loomi-tab></loomi-tabs>Server-side rendering notes
Section titled “Server-side rendering notes”Frameworks such as Next.js, Nuxt, SvelteKit, and Astro sometimes render HTML on the server before browser-only code runs. If your framework complains, move the Loomi import to client-side code. In Next.js, that usually means a component with "use client"; in Nuxt, it often means a .client.ts plugin.
Dependencies
Section titled “Dependencies”@loomidev/core@loomidev/icons