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.
| Name | Type | Default | Description |
|---|---|---|---|
type | 'tabs' | 'pills' | 'accordion' | 'card' | 'tabs' | Visualization of the container: underlined tabs, rounded pills, stacked accordion panels or chromeless always-visible cards. |
vertical | boolean | false | Stacks the header strip vertically beside the content (tabs / pills views). |
justified | boolean | false | Stretches the headers to share the available width equally. |
scrollable | boolean | false | Adds backward/forward scroll buttons when the header strip overflows. |
isKeysAllowed | boolean | true | Enables keyboard navigation (arrows / Home / End / Delete). |
multiple | boolean | false | Allows 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. |
flush | boolean | false | On <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. |
variant | HubPanelVariant | string | undefined | hub-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. |
bindValue | string | undefined | Dot-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 === b | Equality used to match form values against panel values. |
heading | string | undefined | hub-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 | HubPanelVariant | string | undefined | hub-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. |
fill | boolean | false | Card 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. |
standalone | boolean (static attribute) | false | Card 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. |
id | string | '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. |
value | unknown | id | hub-panel: value contributed when the container is used as a form control. |
active | boolean (model) | false | hub-panel: two-way bound active/expanded state. |
disabled | boolean | false | hub-panel: prevents the panel from being activated. |
removable | boolean | false | hub-panel: shows a ✕ affordance and enables removal with the Delete key. |
removeLabel | string | 'Remove panel' | hub-panel: accessible name (aria-label) announced for the ✕ remove button; override it to localize the control. |
routerLink | string | string[] | undefined | hub-panel: turns the panel into a routed panel backed by a <router-outlet>. |
queryParams | Params | undefined | hub-panel: query params appended when navigating to routerLink. |
pathMatch | 'route' | 'full' | 'route' | hub-panel: URL comparison used to mark a routed panel active. |
customClass | string | undefined | hub-panel: extra CSS classes applied to both the nav item and the pane. |
items | HubTabNavItem[] | [] | 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. |
active | unknown (model) | undefined | Two-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'. |
justified | boolean | false | When true, the <hub-tab-nav> tabs stretch to share the available width equally. |
vertical | boolean | false | When true, the <hub-tab-nav> strip stacks its tabs vertically instead of laying them out in a row. |
mode | HubSidePanelMode | '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. |
position | HubSidePanelPosition | '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". |
open | boolean (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. |
closeOnEscape | boolean | true | <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. |
breakpoint | number | 768 | <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. |
autoFocus | boolean | false | <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. |
role | HubSidePanelRole | 'complementary' | <hub-side-panel>: landmark role — 'complementary' beside the page's main content, 'region' when the panel sits inside it. |
ariaLabel | string | undefined | <hub-side-panel>: accessible name of the landmark. Set this or ariaLabelledBy. |
ariaLabelledBy | string | undefined | <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.
| Name | Type | Description |
|---|---|---|
panelChange | OutputEmitterRef<PanelChangeEvent> | hub-panels: emitted when the user activates (opens) a different panel. Provides { current, prev }. |
selectPanel | OutputEmitterRef<HubPanelComponent> | hub-panel: emitted when the panel becomes active. |
deselectPanel | OutputEmitterRef<HubPanelComponent> | hub-panel: emitted when the panel stops being active. |
removed | OutputEmitterRef<HubPanelComponent> | hub-panel: emitted when the panel is removed via the ✕ button or the Delete key. |
activeChange | OutputEmitterRef<boolean> | hub-panel: change half of the two-way active model, emitted with the panel's new active/expanded state. |
activeChange | OutputEmitterRef<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. |
openChange | OutputEmitterRef<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.
| Name | Signature | Returns | Description |
|---|---|---|---|
HubPanelsComponent.selectPanel | selectPanel(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.togglePanel | togglePanel(panel: HubPanelComponent): void | — | Toggles a panel in the accordion view, honouring multiple: with multiple off, opening one panel collapses the rest. |
HubPanelsComponent.removePanel | removePanel(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.removePanelAndRefocus | removePanelAndRefocus(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.toggle | toggle(force?: boolean): void | — | Opens or closes the panel; pass true or false to force a state instead of flipping it. |
HubSidePanelComponent.close | close(): 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.
| Name | Description | Example |
|---|---|---|
hubPanelHeading | Marks 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 / hubPanelFooter | Mark 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> |