ng-hub-ui-panels
v22.1.1
Published
A versatile, accessible content-panels container for Angular with four visualizations (tabs, pills, accordion, card), standalone cards, content header/footer slots, routing, reactive forms and keyboard navigation. Part of the ng-hub-ui family.
Downloads
356
Maintainers
Readme
ng-hub-ui-panels
Español | English
A versatile, accessible content-panels container for Angular that renders as tabs, pills, an accordion or plain cards from a single API — with routing, reactive forms, keyboard navigation and CSS-variable theming. Built as standalone Angular components on top of Signals.
ng-hub-ui-panelssupersedesng-hub-ui-accordion. Its accordion view is a drop-in, more capable replacement.
Documentation and Live Examples
This package is part of Hub UI, a collection of Angular component libraries for standalone apps.
- Docs: https://hubui.dev/panels/overview/
- Live examples: https://hubui.dev/panels/examples/
- Hub UI: https://hubui.dev/
🧩 Library Family ng-hub-ui
This library is part of the ng-hub-ui ecosystem:
- ng-hub-ui-accordion (deprecated → use panels)
- ng-hub-ui-action-sheet
- ng-hub-ui-avatar
- ng-hub-ui-board
- ng-hub-ui-breadcrumbs
- ng-hub-ui-calendar
- ng-hub-ui-dropdown
- ng-hub-ui-ds
- ng-hub-ui-forms
- ng-hub-ui-history
- ng-hub-ui-milestones
- ng-hub-ui-modal
- ng-hub-ui-nav
- ng-hub-ui-paginable
- ng-hub-ui-panels ← You are here
- ng-hub-ui-portal
- ng-hub-ui-skeleton
- ng-hub-ui-sortable
- ng-hub-ui-stepper
- ng-hub-ui-utils
🚀 Quick Start
1. Install
npm install ng-hub-ui-panelsTheming (recommended): install the shared design tokens once so panels — and every other ng-hub-ui library — reads the same palette and dark mode:
npm install ng-hub-ui-ds@import 'ng-hub-ui-ds/styles/tokens/hub-tokens.css';It is an optional peer dependency: panels ships sensible fallbacks and works without it, but the tokens give consistent, themeable colours across the whole family (and power the alert variants).
2. Import
The components are standalone — import them directly where you use them:
import {
PanelsComponent,
PanelComponent,
PanelHeadingDirective,
PanelHeaderDirective,
PanelFooterDirective
} from 'ng-hub-ui-panels';3. Use
<hub-panels>
<hub-panel heading="Overview">First panel content</hub-panel>
<hub-panel heading="Details">Second panel content</hub-panel>
<hub-panel heading="Settings">Third panel content</hub-panel>
</hub-panels>📦 Description
ng-hub-ui-panels unifies the most common content-switching patterns — tabs,
pills and accordion — plus a chromeless card layout, behind one
declarative component. Drop <hub-panel> panes inside <hub-panels> and pick a
type; everything else (keyboard navigation, ARIA wiring, animated collapse,
routed panels and form binding) works the same across views. A <hub-panel> can
also be used standalone, outside any container, where it renders as a card on
its own.
🎯 Features
- Four visualizations —
tabs,pills,accordionandcard, switched with a singletypeinput. - Card layout & standalone —
type="card"renders every panel as an always-visible card; a single<hub-panel>also works on its own, outside any container. - Content header/footer slots —
hubPanelHeaderandhubPanelFootermark header/footer bands that render in every view (distinct from thehubPanelHeadingnav label). - Semantic alerts —
appearance="alert"with avariantturns a panel into a themed callout (role="alert") driven by the design-system semantic tokens, no per-colour CSS. - Strip accent —
<hub-panels variant>recolours the navigation strip (active/hover tab, active pill, active accordion header) from a single semantic accent; built-in variants use the exact design-system tints and any custom accent is picked up automatically. - Forms — implements
ControlValueAccessor; bind the active panel(s) to aFormControlorngModel(single ormultiple), withbindValueandcompareWith. - Routing — a panel with a
routerLinkturns the content area into a<router-outlet>that follows the URL. - Keyboard & a11y — roving tabindex, Arrow/Home/End/Delete keys, and correct
role="tablist"/tab/tabpaneland accordionaria-expanded/aria-controlssemantics. - Strip layout —
vertical,justifiedandscrollableheader strips. - Accordion options —
multipleexpansion and edge-to-edgeflushlayout, with an animated grid-based collapse. - Custom headers — project any markup with the
hubPanelHeadingdirective. - Removable panels — opt-in
removablepanels close with a ✕ button or the Delete key. - Theming — every token is a
--hub-panels-*CSS custom property; the accordion view also honours the--hub-accordion-*contract.
📦 Installation
npm install ng-hub-ui-panelsPeer Dependencies
{
"@angular/common": ">=21.0.0",
"@angular/core": ">=21.0.0",
"@angular/forms": ">=21.0.0",
"@angular/router": ">=21.0.0"
}⚙️ Usage
Tabs (default)
<hub-panels>
<hub-panel heading="One">First</hub-panel>
<hub-panel heading="Two">Second</hub-panel>
</hub-panels>Pills
<hub-panels type="pills"> … </hub-panels>Accordion
<hub-panels type="accordion" multiple flush>
<hub-panel heading="Shipping">…</hub-panel>
<hub-panel heading="Returns">…</hub-panel>
</hub-panels>Cards
type="card" drops the navigation strip entirely: every panel is always visible
and rendered as a card. Use the hubPanelHeader / hubPanelFooter slots for the
card's header and footer bands.
<hub-panels type="card">
<hub-panel>
<div hubPanelHeader>Project summary</div>
Every panel is always visible and styled as a card.
<div hubPanelFooter>Updated 2 hours ago</div>
</hub-panel>
<hub-panel>
<div hubPanelHeader>Team</div>
The same header/footer slots work in tabs, pills and accordion too.
</hub-panel>
</hub-panels>A single <hub-panel> can be used on its own, with no container — it renders as a
card by itself:
<hub-panel>
<div hubPanelHeader>Standalone card</div>
Dropped on its own, a hub-panel renders as a card.
<div hubPanelFooter>
<button class="btn btn-sm btn-primary">Action</button>
</div>
</hub-panel>
hubPanelHeader/hubPanelFooterare content bands inside the panel body and render in every view. They are different fromhubPanelHeading, which is the navigational tab label / accordion disclosure button.
Alerts
A standalone <hub-panel> becomes a semantic alert with
appearance="alert" and a variant. Each variant maps to the design-system
--hub-sys-color-<variant>-* token family — there is no per-colour token set —
so the alert inherits every theme and dark mode automatically.
<hub-panel appearance="alert" variant="success">Your changes were saved.</hub-panel>
<hub-panel appearance="alert" variant="danger">Something went wrong.</hub-panel>
<hub-panel appearance="alert" variant="warning">Your trial ends in 3 days.</hub-panel>
<hub-panel appearance="alert" variant="info">A new version is available.</hub-panel>
<!-- Omit the variant for a neutral alert -->
<hub-panel appearance="alert">A neutral notice.</hub-panel>
<!-- Alerts support the same header/footer slots as cards -->
<hub-panel appearance="alert" variant="danger">
<div hubPanelHeader>Payment failed</div>
Update your billing details to keep your subscription active.
<div hubPanelFooter><button class="btn btn-sm btn-danger">Update billing</button></div>
</hub-panel>
variantaccepts the built-inprimary | success | danger | warning | info(rendered with the exact design-system tints) or any custom string: the alert reads--hub-sys-color-<variant>from your app and derives its look withcolor-mix, so your own accent palette works with no changes here.<!-- with `:root { --hub-sys-color-brand: #9333ea; }` defined in your app --> <hub-panel appearance="alert" variant="brand">On-brand callout</hub-panel>The alert appearance is ignored in the
tabs/pills/accordionstrip views.
Strip accent (variant)
Give <hub-panels> a variant to set the semantic accent of the navigation
strip — the active/hover tab, the active pill and the active accordion header
all follow it. It re-bases a single --hub-panels-accent (with derived
-emphasis / -subtle roles), so changing one accent recolours the whole strip.
<hub-panels variant="success"> … </hub-panels>
<hub-panels type="pills" variant="danger"> … </hub-panels>
<hub-panels type="accordion" variant="info"> … </hub-panels>
variantaccepts the built-inprimary | success | danger | warning | info(rendered with the exact design-system tints) or any custom string: the strip reads--hub-sys-color-<variant>from your app and derives the hover/active roles withcolor-mix, so your own accent palette works with no changes here. Defaults toprimarywhen omitted. Same open-set pattern as the<hub-panel appearance="alert">accent.
Vertical / Justified / Scrollable
<hub-panels vertical> … </hub-panels>
<hub-panels justified> … </hub-panels>
<div style="max-width: 360px">
<hub-panels scrollable> … many panels … </hub-panels>
</div>Reactive forms
<hub-panels [formControl]="selected">
<hub-panel heading="Light" value="light">…</hub-panel>
<hub-panel heading="Dark" value="dark">…</hub-panel>
</hub-panels>selected = new FormControl<string>('dark');With multiple, the form value is an array. Use bindValue="meta.key" to map
each panel's value object to a primitive, and compareWith for custom equality.
Multiple selection
Add multiple to let several panels be open at once. In the tabs / pills
views the open panes render side by side (or stacked, when vertical), each at
least --hub-panels-pane-min-width wide; the content area scrolls when they
overflow. In the accordion view every selected panel expands.
<hub-panels multiple [formControl]="open">
<hub-panel heading="Summary" value="summary">…</hub-panel>
<hub-panel heading="Stats" value="stats">…</hub-panel>
<hub-panel heading="Activity" value="activity">…</hub-panel>
</hub-panels>open = new FormControl<string[]>(['summary', 'stats']);Routed panels
<hub-panels>
<hub-panel heading="Profile" routerLink="/account/profile">Loaded via router-outlet</hub-panel>
<hub-panel heading="Billing" routerLink="/account/billing">Loaded via router-outlet</hub-panel>
</hub-panels>When the active panel is routed, the content area renders a <router-outlet> and
the active panel follows the current URL (tabs / pills views only).
Custom headers
<hub-panel>
<ng-template hubPanelHeading>
<i class="fa-solid fa-gear"></i> Settings <span class="badge text-bg-primary">3</span>
</ng-template>
Panel content
</hub-panel>🪄 API Reference
<hub-panels> inputs
| Input | Type | Default | Description |
| --- | --- | --- | --- |
| type | 'tabs' \| 'pills' \| 'accordion' \| 'card' | 'tabs' | Visualization of the container. card removes the strip and shows every panel as a card. |
| vertical | boolean | false | Stacks the strip beside the content (tabs / pills). |
| justified | boolean | false | Stretches headers to equal width. |
| scrollable | boolean | false | Adds scroll buttons when the strip overflows. |
| isKeysAllowed | boolean | true | Enables keyboard navigation. |
| multiple | boolean | false | Accordion: allow several panels expanded at once. |
| flush | boolean | false | Accordion: edge-to-edge layout without outer chrome. |
| bindValue | string | undefined | Dot-notation path applied to each panel value. |
| compareWith | (a, b) => boolean | === | Equality used to match form values. |
<hub-panels> outputs
| Output | Payload | Description |
| --- | --- | --- |
| panelChange | PanelChangeEvent | Emitted when a different panel is opened ({ current, prev }). |
<hub-panel> inputs
| Input | Type | Default | Description |
| --- | --- | --- | --- |
| heading | string | undefined | Plain-text header (ignored when a hubPanelHeading template is present). |
| id | string | auto | ARIA pairing id. |
| value | unknown | id | Value contributed to the form control. |
| active | boolean (model) | false | Two-way active/expanded state. |
| disabled | boolean | false | Prevents activation. |
| removable | boolean | false | Shows a ✕ and enables the Delete key. |
| routerLink | string \| string[] | undefined | Turns the panel into a routed panel. |
| queryParams | Params | undefined | Query params for routerLink. |
| pathMatch | 'route' \| 'full' | 'route' | URL comparison for routed panels. |
| customClass | string | undefined | Extra classes on the nav item and pane. |
<hub-panel> outputs
| Output | Payload | Description |
| --- | --- | --- |
| selectPanel | PanelComponent | Emitted when the panel becomes active. |
| deselectPanel | PanelComponent | Emitted when the panel stops being active. |
| removed | PanelComponent | Emitted on removal (✕ or Delete). |
Directives
hubPanelHeading— marks an<ng-template>inside ahub-panelas its custom navigational header (tab/pill link or accordion disclosure button).hubPanelHeader— marks an element inside ahub-panelas the content header band, rendered at the top of the panel body in every view.hubPanelFooter— marks an element inside ahub-panelas the content footer band, rendered at the bottom of the panel body in every view.
Configuration
Provide PanelsConfig to change defaults application-wide:
providers: [{ provide: PanelsConfig, useValue: { ...new PanelsConfig(), type: 'pills' } }];🎨 Styling
Everything is themed through --hub-panels-* CSS custom properties. See
docs/css-variables-reference.md for the full list.
hub-panels {
--hub-panels-tab-color-active: #198754;
--hub-panels-pill-bg-active: #198754;
}The card view and the header/footer bands have their own tokens
(--hub-panels-card-*, --hub-panels-card-gap, --hub-panels-panel-header-*).
Because a standalone <hub-panel> has no .hub-panels ancestor, target the panel
itself when theming standalone cards:
hub-panel {
--hub-panels-card-border-radius: 0.75rem;
--hub-panels-panel-header-bg: #eef2ff;
}The accordion view also reads the --hub-accordion-* contract, so themes written
for ng-hub-ui-accordion keep working.
♿ Accessibility
tabs/pills:role="tablist",role="tab",role="tabpanel", roving tabindex andaria-selected.accordion: a disclosure button per panel witharia-expanded/aria-controlsand an inert collapsed region.- Keyboard: Arrow keys, Home, End move focus; Delete removes a
removablepanel; Enter/Space toggle accordion headers.
📚 Migration from ng-hub-ui-accordion
ng-hub-ui-panels replaces ng-hub-ui-accordion. Map the markup as follows:
| Accordion | Panels |
| --- | --- |
| <hub-accordion [multiple]="true"> | <hub-panels type="accordion" multiple> |
| <hub-accordion [options]="{ flush: true }"> | <hub-panels type="accordion" flush> |
| <hub-accordion-panel title="…"> | <hub-panel heading="…"> |
| <ng-template hubAccordionPanelHeader> | <ng-template hubPanelHeading> |
| (collapsedChange) | (panelChange) |
Form binding (formControl / ngModel, value, bindValue, compareWith) works
the same. Existing --hub-accordion-* theme overrides keep applying.
📊 Changelog
See CHANGELOG.md.
📄 License
MIT © Carlos Morcillo
