jolly-tabs ​
jolly-tabs renders direct jolly-tab children as an accessible tab set.
<jolly-tabs value="build">
<jolly-tab value="build" label="Build">Build settings</jolly-tab>
<jolly-tab value="paint" label="Paint">Paint settings</jolly-tab>
</jolly-tabs>Properties ​
interface Tabs {
value: string;
orientation: "horizontal" | "vertical"; // default "horizontal"
variant: "default" | "skew" | "line"; // default "default"
reorderable: boolean;
}- An absent, disabled or unknown
valueselects the first enabled tab, once the tabs are slotted. A value set before that is kept. - A tab appended in the same turn as the value selecting it stays selected.
Events ​
interface TabsEvents {
"jolly-tab-change": { value: string; }; // user selection
"jolly-tab-close": { value: string; };
"jolly-tab-action": { value: string; };
"jolly-tab-edit": { value: string; };
"jolly-tab-reorder": { value: string; index: number; };
}The element never removes or moves a tab itself: the listener does.
jolly-tab-editasks the host to rename or edit a tab. It fires on a double click, after the first click selected the tab, and on F2 for the focused tab. A disabled tab fires it too without being selected.- A double click leaves the tab unfocused, so focus restored to it later, such as by a closing dialog, draws no focus ring. F2 keeps the focus.
Keyboard ​
Home, End and the arrow keys of the orientation move between enabled tabs. F2 emits jolly-tab-edit for the focused tab.
Closable tabs ​
- A
closabletab has a close button inside it. It is skipped by keyboard focus. - Clicking it, or middle-clicking the tab, emits
jolly-tab-closeand does not select the tab.
tabs.addEventListener("jolly-tab-close", (event) => {
const closed = [...tabs.querySelectorAll("jolly-tab")].find(
(tab) => tab.value === event.detail.value
);
closed?.remove();
});Reordering ​
- In a
reorderableset, the user drags a tab along the strip. The other tabs slide aside. - Dropping emits
jolly-tab-reorder.indexis the position the tab asks for. - Move the
jolly-tabbefore the listener returns, and it glides into place. A listener that moves nothing, or Escape, slides every tab back. - A
fixedtab cannot be dragged and keeps its index.
tabs.addEventListener("jolly-tab-reorder", (event) => {
const tabList = [...tabs.querySelectorAll("jolly-tab")];
const moved = tabList.find((tab) => tab.value === event.detail.value);
const rest = tabList.filter((tab) => tab !== moved);
tabs.insertBefore(moved, rest[event.detail.index] ?? null);
});Tab actions ​
- A tab with an
actionicon has a secondary button inside it. - Clicking it emits
jolly-tab-actionand does not select the tab. - It shows on the selected tab and on disabled tabs, stays enabled on a disabled tab, and is skipped by keyboard focus.
<jolly-tab value="grass" label="grass" badge="12" action="pencil" action-label="Edit"></jolly-tab>Trailing controls ​
The list-end slot sits after the last tab, outside the scroll area, so it stays visible when the tabs overflow.
<jolly-tabs>
<jolly-tab value="grass" label="grass"></jolly-tab>
<jolly-button slot="list-end" icon="plus" icon-only label="Add"></jolly-button>
</jolly-tabs>With empty panels, jolly-tabs works as a plain strip: the panel area collapses to zero height.
Skew variant ​
variant="skew" draws taller tabs as a chain of parallelograms, for a horizontal strip at the start of its row. A list-end control joins the chain.
jolly-tabs {
--jolly-tab-skew: 8px; /* horizontal run of the slant */
--jolly-tab-skew-seam: 2px; /* gap between two tabs */
}Line variant ​
variant="line" shrinks each tab to an unlabelled segment of a thin bar, for a horizontal strip that must take almost no height.
- Segments share the strip width, up to a maximum width each.
- The label, and the
tooltipunder it, show in a tip below the segment on hover or keyboard focus. The label stays the accessible name. - Badges, actions, close buttons and status marks are hidden.
jolly-tabs {
--jolly-tab-line-size: 3px; /* bar thickness */
--jolly-tab-line-hit: 8px; /* segment height, the pointer target */
--jolly-tab-line-gap: 2px;
--jolly-tab-line-max-width: 120px;
--jolly-tab-line-fill: rgb(128 128 128 / 15%);
--jolly-tab-line-fill-hover: #7b828c;
}Parts ​
strip: the tab list plus thelist-endslot.list: the scrollable tablist.tab,tab-selected,tab-dragging: a tab item, with its buttons.badge: the badge chip.action,action-selected: the action button.close,close-selected: the close button.modified,busy,failed: the status marks.tip: the hover label of the line variant.
Tabs size to their label. Stretch them with:
jolly-tabs::part(tab) {
flex: 1 1 0;
}Styling ​
A tab is at least --jolly-control-height tall. The strip has no resting fill: hover tints neutral, selection tints with the accent and adds a 2px accent bar.
jolly-tabs {
--jolly-tab-icon-size: 16px;
--jolly-tab-close-size: 14px;
--jolly-tab-close-icon-size: 8px;
--jolly-tab-modified-size: 8px;
--jolly-tab-status-size: 10px;
}Colour tokens: --jolly-tab-bg-hover, --jolly-tab-selected-bg, --jolly-tab-selected-bg-hover, --jolly-tab-close-bg-hover, --jolly-tab-close-fg-hover, --jolly-tab-badge-bg and --jolly-tab-badge-fg.