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 40 inputs. Bind them like any Angular @Input.

NameTypeDefaultDescription
type'tabs' | 'pills' | 'accordion' | 'card''tabs'Visualization of the container: underlined tabs, rounded pills, stacked accordion panels or chromeless always-visible cards.
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 HubPanelsConfig.togglePosition.
variantHubPanelVariant | stringundefinedhub-panels: semantic accent of the navigation strip — the active/hover tab, the active pill and the active accordion header all follow it. The built-in values ('primary' | 'success' | 'danger' | 'warning' | 'info') use the design-system tints, but any string is accepted: the strip reads --hub-sys-color-<variant> from your application. Defaults to primary.
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.
variantHubPanelVariant | stringundefinedhub-panel: semantic accent of the panel — it colours an alert and tints a whole card (reflected as data-variant). The built-in values ('primary' | 'success' | 'danger' | 'warning' | 'info') use the design-system tints, but any string is accepted: the panel reads --hub-sys-color-<variant> from your application. Omit it for a neutral panel.
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.
idstring'hub-panel-<n>'hub-panel: id used for the ARIA pairing between a tab and its panel (or an accordion header and its region). Auto-generated as hub-panel-<n>; set it when you need stable ids, for a deep link or an aria-controls of your own.
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.
modeHubSidePanelMode'side'<hub-side-panel>: 'side' docks the panel and narrows the content to make room; 'over' floats it over the content edge with no backdrop, scroll lock or focus trap.
positionHubSidePanelPosition'end'<hub-side-panel>: logical inline edge of the container the panel sits on — 'end' is the right edge in LTR and the left edge under dir="rtl".
openboolean (model)false<hub-side-panel>: two-way open state. Closing hides the panel without destroying its projected content, so a chat or a form inside keeps its state.
closeOnEscapebooleantrue<hub-side-panel>: whether Escape pressed inside the panel closes it. Escape pressed on the page is never taken, and a control that already consumed the key keeps the panel open.
breakpointnumber768<hub-side-panel>: container inline size in px below which a side panel renders as over, because a docked panel on a narrow screen would crush the content. 0 keeps it docked at any width.
autoFocusbooleanfalse<hub-side-panel>: whether opening moves focus into the panel — to the first [autofocus] element, else the first tabbable one, else the panel. Off by default.
roleHubSidePanelRole'complementary'<hub-side-panel>: landmark role — 'complementary' beside the page's main content, 'region' when the panel sits inside it.
ariaLabelstringundefined<hub-side-panel>: accessible name of the landmark. Set this or ariaLabelledBy.
ariaLabelledBystringundefined<hub-side-panel>: id of the element that names the landmark, typically the heading in the header slot.

Outputs

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

NameTypeDescription
panelChangeOutputEmitterRef<PanelChangeEvent>hub-panels: emitted when the user activates (opens) a different panel. Provides { current, prev }.
selectPanelOutputEmitterRef<HubPanelComponent>hub-panel: emitted when the panel becomes active.
deselectPanelOutputEmitterRef<HubPanelComponent>hub-panel: emitted when the panel stops being active.
removedOutputEmitterRef<HubPanelComponent>hub-panel: emitted when the panel is removed via the ✕ button or the Delete key.
activeChangeOutputEmitterRef<boolean>hub-panel: change half of the two-way active model, emitted with the panel's new active/expanded state.
activeChangeOutputEmitterRef<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.
openChangeOutputEmitterRef<boolean><hub-side-panel>: change half of the two-way open model; emitted with the new state whoever changed it (binding, Escape, toggle() or close()).

Methods

Drive panels from code — 6 methods on its programmatic surface.

NameSignatureReturnsDescription
HubPanelsComponent.selectPanelselectPanel(panel: HubPanelComponent): void—Activates a panel as a user click would: marks it active, navigates when the panel is routed and emits panelChange. Disabled panels are a no-op.
HubPanelsComponent.togglePaneltogglePanel(panel: HubPanelComponent): void—Toggles a panel in the accordion view, honouring multiple: with multiple off, opening one panel collapses the rest.
HubPanelsComponent.removePanelremovePanel(panel: HubPanelComponent, options?: { reselect?: boolean; emit?: boolean }): void—Removes a panel from the group. reselect decides whether another panel takes its place, emit whether the removal notifies through panelChange.
HubPanelsComponent.removePanelAndRefocusremovePanelAndRefocus(panel: HubPanelComponent): void—Removes a panel and hands keyboard focus to the closest remaining header, so a removal never drops focus to the document body. This is what the ✕ button and the Delete key call.
HubSidePanelComponent.toggletoggle(force?: boolean): void—Opens or closes the panel; pass true or false to force a state instead of flipping it.
HubSidePanelComponent.closeclose(): void—Closes the panel. If focus was inside, it returns to the element that had it when the panel opened.

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 <hub-badge color="primary" shape="rounded">3</hub-badge> </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>