GrTabsWithPanels
Pairs a tab row with its panels under one model and one id base.
Overview
When to take it
- both the tabs and the panels are needed — the pair is described by one component, without two identical values in two places;
- the
tab↔tabpanellink must be correct by default —idBaseis generated inside, it cannot be forgotten; - the tabs and the panels stand next to each other — horizontally, or at the side with
orientation="vertical".
When to take something else
| Need | Take |
|---|---|
| Only the row of tabs, the panels are your own | GrTabs |
| Only the panels: the row of tabs is drawn differently | GrTabPanels |
| The tabs and the panels stand in different places of the layout | the pair GrTabs + GrTabPanels |
| The sections expand rather than switch | GrCollapse |
| The view of one piece of content is switched | GrSegmented |
Variants every tone and view, rendered by the real component
variant
pillsline
Sizes
xssmmdlg
The grid is live: hover and press it. Hover, focus and active states are not drawn here — they belong to the browser, not to a prop.
Playground controls come from the API, not from hand
Loading…
0 changed· state is in the URL
<GrTabsWithPanels :tabs="tabs" v-model="value" />Examples 2 each with a live demo and its code
A product editor in four tabs
The row and its panels are one component: the ids and
aria-controls↔aria-labelledbyare wired inside, so there is no second value to keep in step. Row props —variant,size,tabs— pass straight through, and a tab with a problem says so with its counter. Every panel holds a form, sokeep-alivekeeps what was typed when the tab changes.Oak writing desk · draft Must be above the current price.Low stock — the shop shows «Only a few left»
shop.example/Account settings in a column
Settings are a column:
orientation="vertical"puts the row at the side and the panel beside it. The alert above refers to the security tab from outside, so theid-baseis explicit — the page knows the tab’s id and moves the focus to it. The counter on «Security» goes away once two-factor sign-in is on.Two-factor sign-in is offAnyone with your password can sign in to the billing workspace.
API generated from the package · cannot drift
Props
| Prop | Type | default | Description |
|---|---|---|---|
variant | "pills" | "line" | undefined | undefined | The kind of row: a holder with pills or a row with an underline. |
closable | boolean | undefined | undefined | A cross on the tabs and closing by `Delete`/`Backspace`. Lifted pointwise with `closable: false` on the tab itself. |
size | "xs" | "sm" | "md" | "lg" | undefined | undefined | The height of the tabs and the type size of the label by the controls scale. `md` by default. |
orientation | GrTabsOrientation | undefined | undefined | A horizontal (the default) or vertical list of tabs. |
activationMode | GrTabsActivationMode | undefined | undefined | `automatic` (the default) — an arrow switches the tab at once; `manual` — an arrow only moves the focus, and the choice is confirmed with `Enter`/`Space`. The second mode is for tabs with heavy loading: otherwise walking through with the arrows pulls every panel. |
emptyText | string | undefined | undefined | The text when the list of tabs is empty. The `#empty` slot is stronger. |
idBase | string | undefined | undefined | The base of the ids for the ARIA link with `GrTabPanels`. If it is set, every tab gets `id="<idBase>-tab-<value>"` and `aria-controls="<idBase>-panel-<value>"`. Pass the same `idBase` into `GrTabPanels` to link `tab`↔`tabpanel`. |
modelValuerequiredv-model | string | — | The `value` of the selected tab (`v-model`). If it matches none, `Tab` lands on the first available one. |
tabsrequired | GrTab[] | — | The tabs in display order: `value`, `label`, optionally an icon, a counter and `disabled`. |
Slots
| Slot | Type | Description |
|---|---|---|
default | any | The panels (`GrTabPanel`). |
tab | { tab: GrTab; active: boolean; disabled: boolean; } | The content of a tab as a whole — goes to `GrTabs`. |
empty | any | The empty row of tabs — goes to `GrTabs`. |
Events
| Event | Signature | Description |
|---|---|---|
close | (event: "close", value: string): void | — |
update:modelValue | (event: "update:modelValue", value: string): void | — |
Types
Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.
import type { GrTabsWithPanelsProps, GrTabsWithPanelsEmits } from '@feugene/granularity/components/GrTabsWithPanels'Filedist/types/components/GrTabsWithPanels/config.d.ts
const grTabsWithPanelsConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrTabsWithPanels">;Filedist/types/components/GrTabsWithPanels/GrTabsWithPanels.vue.d.ts
interface GrTabsWithPanelsProps extends GrTabsProps {
}interface GrTabsWithPanelsEmits {
(e: 'update:modelValue', value: string): void;
(e: 'close', value: string): void;
}Usage from the library prose
Why it exists
The pair requires two identical values in two places — the active tab and the idBase from which
the tab ↔ tabpanel link is built. Both are duplicated, and both diverge silently.
How far from theoretical that is has already been measured: GrTabs puts an id on the tabs
only with an explicit idBase, so in the usual case — when it is not passed at all — the
aria-labelledby of a panel pointed at a non-existent element. Here both values belong to one
component, and they cannot be forgotten.
<GrTabsWithPanels v-model="tab" :tabs="tabs">
<GrTabPanel value="profile">…</GrTabPanel>
<GrTabPanel value="billing">…</GrTabPanel>
</GrTabsWithPanels>idBase is accepted as a prop but is not required: without it one of its own is generated. An
explicit one is needed where those ids are referenced from outside.
The props of the row are passed through as they are
size, variant, activationMode, orientation, closable, emptyText and tabs go to
GrTabs; the #tab and #empty slots go there too, and #default — to the panels. The close
event bubbles outward.
A passed-through prop that was not set arrives in GrTabs absent rather than with the value of
the wrapper. For size and variant that is a matter of principle: their default lives in the
resolver of GrConfigProvider, and a substituted value would override the provider — the panel
would stop listening to the global setting.
The surface of the props is held by a gate: it must match GrTabs minus what the wrapper owns
itself. Otherwise the row would gain a prop the wrapper knows nothing about — the very silent
divergence the component was made for.
The layout
By default the row is on top, the panels under it. orientation="vertical" puts the row at the
side: vertical tabs above the panels would look like a column of links with no connection to the
content.
Props in this example