GrTabPanels

  • core
  • navigation
  • API · 2 props

Renders tab panels linked to `GrTabs` by ARIA for accessible tabs.

import { GrTabPanels } from '@feugene/granularity/components/GrTabPanels'

Overview

When to take it

  • the tabs need panels — the component links them to GrTabs by ARIA, with no manual ids;
  • a panel is heavy — lazy mounting does not build the content until the first showing;
  • the state of a panel has to be kept — a hidden panel stays mounted, and what was typed into it is not lost;
  • the layout of the tabs is your own — the panels stand separately from the list of tabs rather than under it.

When to take something else

NeedTake
The list of tabs itself is neededGrTabs
The sections expand rather than switchGrCollapse
The view of one piece of content is switchedGrSegmented

Playground controls come from the API, not from hand

Stage

Loading…

0 changed· state is in the URL

Examples 1 each with a live demo and its code

  • Tabs in the header, panels in the content

    The row lives in the page header and the panels far below it, so they are two components with one model and one id-base — that is what gives each panel aria-labelledby on its tab. The invoice history is lazy and keep-alive: built on the first visit and kept hidden afterwards, so the query does not run again on return. The notes are keep-alive as well — a draft typed there survives a trip to another tab.

    Fabrikam GmbHCustomer since March 2024 · Team plan

API generated from the package · cannot drift

Props

PropTypedefaultDescription
idBasestring | undefinedundefinedThe id base shared with `GrTabs`: with it the panels get `aria-labelledby` pointing to their tabs. Without it the id is generated, and there is no link to the tabs.
modelValuerequiredstring—The `value` of the visible panel — the same model as in `GrTabs`.

Slots

SlotTypeDescription
defaultanyThe panels (`GrTabPanel`).

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
import type { GrTabPanelsContext, GrTabPanelsProps, GrTabPanelProps } from '@feugene/granularity/components/GrTabPanels'

Filedist/types/components/GrTabPanels/context.d.ts

GrTabPanelsContextinterface
interface GrTabPanelsContext {
    activeValue: ComputedRef<string>;
    idBase: ComputedRef<string>;
    tabsLinked: ComputedRef<boolean>;
}

Filedist/types/components/GrTabPanels/config.d.ts

grTabPanelsConfigconst
const grTabPanelsConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrTabPanels">;

Filedist/types/components/GrTabPanels/GrTabPanels.vue.d.ts

GrTabPanelsPropsinterface
interface GrTabPanelsProps {
    modelValue: string;
    idBase?: string;
}

Filedist/types/components/GrTabPanels/GrTabPanel.vue.d.ts

GrTabPanelPropsinterface
interface GrTabPanelProps {
    value: string;
    keepAlive?: boolean;
    lazy?: boolean;
}

Usage from the library prose

The pairing is held by a shared idBase
vue
<GrTabs v-model="tab" :tabs="tabs" id-base="settings" />

<GrTabPanels v-model="tab" id-base="settings">
  <GrTabPanel value="profile">…</GrTabPanel>
  <GrTabPanel value="billing">…</GrTabPanel>
</GrTabPanels>

idBase is reactive: a change of it reaches the panels. It used to be computed once at setup — the panels were left with the old ids, GrTabs moved to the new ones, and aria-controls ↔ aria-labelledby diverged silently.

Without idBase it is generated by itself — that is enough for the panel’s own id, but there will be no link to the tab: GrTabs puts ids on the tabs only with an explicit idBase. In that case the panel does not set aria-labelledby at all. A reference into the void is worse than its absence: the narrator would announce the name of the panel as an empty string instead of “no name”, and the consumer would no longer be able to fix that. In a dev build the case is reported with a warning — the same device with which GrFormField catches a forgotten control.

Switching the panel

The incoming panel appears over --gr-duration-fast, sliding up slightly from below. The outgoing one disappears instantly, and that is not an economy: were the container to hold both panels at once, for the duration of the transition it would grow by the height of both and jolt the page.

With keepAlive there is no transition at all — the panel does not leave the DOM, and hidden is display: none, which cannot be animated in principle. The system “reduce motion” setting suppresses the appearance, as everywhere in the package (docs/motion.md).

keepAlive and lazy

By default an inactive panel is not in the DOM. keepAlive leaves it hidden (hidden) — the state of the forms is not lost. lazy adds “do not mount until it has been shown for the first time” to that: together they give “mount once on demand and never destroy again”.

keepAlivelazyThe behaviour
——the panel is mounted when shown and unmounted when left
✓—all of the panels are in the DOM at once, the inactive ones hidden
✓✓the panel appears in the DOM on the first showing and stays