Angular Tabs, Pills & Accordion | ng-hub-ui-panels

Angular content-panels component that renders as tabs, pills or an accordion, with routing, reactive forms, keyboard navigation and CSS variables.

API reference

Here's the full contract for panels: everything you can bind, listen to, project and theme, gathered in one place. Wire up what you need and style what you want — it's standalone and signal-friendly.

Inputs

Dial panels in with 29 inputs. Bind them like any Angular @Input.

NameTypeDefaultDescription
type'tabs' | 'pills' | 'accordion' | 'card''tabs'Visualization of the container: underlined tabs, rounded pills or stacked accordion panels.
verticalbooleanfalseStacks the header strip vertically beside the content (tabs / pills views).
justifiedbooleanfalseStretches the headers to share the available width equally.
scrollablebooleanfalseAdds backward/forward scroll buttons when the header strip overflows.
isKeysAllowedbooleantrueEnables keyboard navigation (arrows / Home / End / Delete).
multiplebooleanfalseAllows several panels to be active at once. In the accordion view they all expand; in the tabs/pills views every active header starts a visible panel block and the following inactive headers stay grouped above that block until the next active header starts a new one. The form value becomes an array.
flushbooleanfalseOn <hub-panels> (accordion view): edge-to-edge panels without outer borders or radius. On <hub-panel> (card): removes the card body padding so a table/list/media block sits edge-to-edge.
togglePosition'start' | 'end''end'On <hub-panels> (accordion view): which side of the header row the disclosure chevron sits on. "end" (default) trails the heading, "start" leads it. Only the visual order changes — the DOM is untouched — and the offsets are logical, so "start" is the left edge in LTR and the right edge in RTL. Set the app-wide default through PanelsConfig.togglePosition.
bindValuestringundefinedDot-notation path applied to each panel's value to obtain the emitted form value (e.g. 'meta.key').
compareWith(a: unknown, b: unknown) => boolean(a, b) => a === bEquality used to match form values against panel values.
headingstringundefinedhub-panel: plain-text header. Ignored when a hubPanelHeading template is provided.
appearance'card' | 'alert''card'hub-panel: visual appearance of a standalone panel — 'card' (default) or a semantic 'alert' callout. Ignored in the tabs/pills/accordion strip views.
variant'primary' | 'success' | 'danger' | 'warning' | 'info'undefinedhub-panel: semantic colour of an alert ('primary' | 'success' | 'danger' | 'warning' | 'info'). Omit for a neutral alert.
fillbooleanfalseCard panel only: fills the parent's height and lets the body scroll, so an inner flex:1/min-height:0 region (e.g. a tab strip + scroll pane) is bounded. The parent must be a flex column.
standaloneboolean (static attribute)falseCard panel only, static attribute: opts a loose <hub-panel> out of an ancestor tabs/pills/accordion group in the same template so it renders as a plain card instead of being captured as a hidden tab.
valueunknownidhub-panel: value contributed when the container is used as a form control.
activeboolean (model)falsehub-panel: two-way bound active/expanded state.
disabledbooleanfalsehub-panel: prevents the panel from being activated.
removablebooleanfalsehub-panel: shows a ✕ affordance and enables removal with the Delete key.
removeLabelstring'Remove panel'hub-panel: accessible name (aria-label) announced for the ✕ remove button; override it to localize the control.
routerLinkstring | string[]undefinedhub-panel: turns the panel into a routed panel backed by a <router-outlet>.
queryParamsParamsundefinedhub-panel: query params appended when navigating to routerLink.
pathMatch'route' | 'full''route'hub-panel: URL comparison used to mark a routed panel active.
customClassstringundefinedhub-panel: extra CSS classes applied to both the nav item and the pane.
itemsHubTabNavItem[][]The selectable tabs of a <hub-tab-nav>, in render order. Each item is a lightweight { value, label, disabled?, id? } descriptor — the strip owns no content, so value is what it emits when the tab is chosen.
activeunknown (model)undefinedTwo-way bound selected value of a <hub-tab-nav>. Set it to pre-select a tab and read it back through activeChange; the matching item is the value whose value equals this.
appearance'tabs' | 'pills''tabs'Visual appearance of the <hub-tab-nav> strip: classic underlined 'tabs' (default) or rounded 'pills'.
justifiedbooleanfalseWhen true, the <hub-tab-nav> tabs stretch to share the available width equally.
verticalbooleanfalseWhen true, the <hub-tab-nav> strip stacks its tabs vertically instead of laying them out in a row.

Outputs

React to what panels does — 5 events to hook your logic onto.

NameTypeDescription
panelChangeEventEmitter<PanelChangeEvent>hub-panels: emitted when the user activates (opens) a different panel. Provides { current, prev }.
selectPanelEventEmitter<PanelComponent>hub-panel: emitted when the panel becomes active.
deselectPanelEventEmitter<PanelComponent>hub-panel: emitted when the panel stops being active.
removedEventEmitter<PanelComponent>hub-panel: emitted when the panel is removed via the ✕ button or the Delete key.
activeChangeEventEmitter<unknown>Emitted by <hub-tab-nav> whenever the selected value changes (the change half of the two-way active model), carrying the newly selected item's value.

Templates

Make it yours — 3 template slots let you project custom markup.

NameDescriptionExample
hubPanelHeadingMarks an <ng-template> inside a hub-panel as that panel’s custom header (strip link in tabs/pills, disclosure button in accordion), replacing the plain-text heading input.<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>
hubPanelHeader / hubPanelFooterMark an element inside a hub-panel as the panel’s content header/footer band. Renders in every view (tabs, pills, accordion, card), distinct from hubPanelHeading.<hub-panel> <div hubPanelHeader>Card title</div> Card content <div hubPanelFooter>Actions</div> </hub-panel>
type="card"The chromeless card visualization: no navigation strip, every panel always visible. A hub-panel can also be used standalone, outside any hub-panels, where it renders as a card.<hub-panels type="card"> <hub-panel>…</hub-panel> <hub-panel>…</hub-panel> </hub-panels>