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.

Last updated Sep 28, 2026

Overview

Why teams search for this library

Use this Angular panels component when you need tabs, pills and accordion patterns from a single API, with routed panels, reactive-forms binding, keyboard navigation and CSS-variable theming.

Install

npm install ng-hub-ui-panels

Jump to

Ideal for

  • settings pages
  • product detail tabs
  • FAQ accordions
  • dashboard sections

About panels

ng-hub-ui-panels unifies tabs, pills and accordion into one standalone Angular component built on Signals. It supports routed panels, ControlValueAccessor form binding, scrollable and vertical strips, and full --hub-panels-* theming. It supersedes ng-hub-ui-accordion, whose accordion view it replaces while keeping the --hub-accordion-* token contract.

Feature guides

Visualizations

One component, four views: classic underlined tabs, rounded pills, stacked accordion disclosure panels and chromeless always-visible cards — switchable through a single `type` input.

Examples:
Basic Tabs

Basic Tabs

The first enabled panel is activated automatically.
Switch panels with a click or the arrow keys.
Each panel projects its own content.
Pills

Pills

Account settings live here.
Password and two-factor options.
Email and push preferences.
Accordion View

Accordion View

Card & Content Slots

Card & Content Slots

Project summary
No tabs, no pills — every panel is always visible and styled as a card.
Team
The same hubPanelHeader / hubPanelFooter slots work in tabs, pills and accordion too.

A single standalone <hub-panel> (no container):

Standalone card
Dropped on its own, a hub-panel renders as a card by itself.
Card slots (variant, flush, fill)

Card slots (variant, flush, fill)

Success
A plain card honours [variant] — one accent tints the whole frame.
Danger
No colour values added — derived by color-mix like the alert.
Info
Set --hub-panels-card-border-style: dashed for an empty-state look.
Normal padding
Padded body — there is a gap around this strip.
Flush
Flush body — this strip runs edge-to-edge to the card sides.
Fill + scroll

Row 1 — the body scrolls; the header stays put.

Row 2 — the body scrolls; the header stays put.

Row 3 — the body scrolls; the header stays put.

Row 4 — the body scrolls; the header stays put.

Row 5 — the body scrolls; the header stays put.

Row 6 — the body scrolls; the header stays put.

Row 7 — the body scrolls; the header stays put.

Row 8 — the body scrolls; the header stays put.

Row 9 — the body scrolls; the header stays put.

Row 10 — the body scrolls; the header stays put.

Row 11 — the body scrolls; the header stays put.

Row 12 — the body scrolls; the header stays put.

Row 13 — the body scrolls; the header stays put.

Row 14 — the body scrolls; the header stays put.

Row 15 — the body scrolls; the header stays put.

Row 16 — the body scrolls; the header stays put.

Row 17 — the body scrolls; the header stays put.

Row 18 — the body scrolls; the header stays put.

Row 19 — the body scrolls; the header stays put.

Row 20 — the body scrolls; the header stays put.

Semantic Alerts

Semantic Alerts

Heads up — a new version is available.
Your changes were saved successfully.
Your trial ends in 3 days.
Something went wrong while saving.
Tip: you can pin panels for quick access.
A neutral notice with no semantic variant.

An alert can use the same header/footer slots as a card:

Payment failed
We couldn't process your card. Update your billing details to keep your subscription active.
Semantic Strip Accent

Semantic Strip Accent

The active tab follows the success accent.
Switch tabs to see the accent move.
No per-colour CSS — one accent drives it all.
The active pill fills with the danger accent.
Hover the inactive pills too.
Everything derives from a single token.
A custom or built-in variant works the same way.
Active pill uses the warning accent.
Tab nav (content-less strip)

Tab nav (content-less strip)

Overview

The tab strip owns no content — this view is rendered by the host from active().

Selected value: overview

Strip Layout

Lay out the header strip to fit any shell: stack it vertically beside the content, stretch headers to fill the width, or make an overflowing strip scrollable.

Examples:
Vertical Tabs

Vertical Tabs

Your public profile information.
Invoices and payment methods.
Manage members and roles.
Justified Tabs

Justified Tabs

Today's agenda.
The seven-day view.
The full month at a glance.
Scrollable Tabs

Scrollable Tabs

Content for section 1.
Content for section 2.
Content for section 3.
Content for section 4.
Content for section 5.
Content for section 6.
Content for section 7.
Content for section 8.

Panel Management

Disable individual panels and let users add or remove panels on the fly with the ✕ affordance and the Delete key.

Examples:
Disabled Panels

Disabled Panels

This panel is selectable.
This panel is disabled.
Arrow keys skip the disabled panel.
Removable & Dynamic Panels

Removable & Dynamic Panels

Content for Tab 1.
Content for Tab 2.
Content for Tab 3.

Custom Templates

Replace the plain-text header with any Angular template — icons, badges or interactive controls — via the `hubPanelHeading` directive.

Examples:
Custom Heading Templates

Custom Heading Templates

Twelve unread messages.
Messages you have sent.
Recently deleted items.
Accordion Row Actions

Accordion Row Actions

Form Integration

First-class ControlValueAccessor support binds the active panel(s) to Angular's reactive and template-driven forms.

Examples:
Reactive Forms Integration

Reactive Forms Integration

Light theme preview.
Dark theme preview.
Follows the OS setting.
Selected value: "dark"

Multiple Selection

With `multiple`, several panels can be open at once. In the accordion view they all expand; in the tabs/pills views every active header starts its own visible panel while the following inactive headers stay grouped above that panel until the next active header starts a new block.

Examples:
Multiple Selection

Multiple Selection

Click an inactive tab to split the layout into a new panel block. Click an active tab to merge that block back.

Key metrics and a quick overview of the account.
Detailed statistics and charts for the period.
A chronological feed of recent activity.
Free-form notes shared with the team.
Open panels: [ "summary", "stats" ]
Multiple Vertical Headers

Multiple Vertical Headers

Each active header starts a new row-like vertical block. The blocks stack top-to-bottom, and the whole layout scrolls horizontally only when one row needs more width.

Tabs
Public profile details and biography.
Invoices, payment methods and VAT details.
Members, roles and access levels.
Shared internal notes for the account.
Open tabs: [ "profile", "team" ]
Pills
Open items waiting for review.
Approvals and pending checks.
Publication options and scheduling.
Historical records and exported snapshots.
Open pills: [ "inbox", "publish" ]

Accordion Options

The accordion view supports multiple simultaneously expanded panels and an edge-to-edge `flush` layout without outer chrome.

Examples:
Accordion Multiple & Flush

Accordion Multiple & Flush

Standard delivery in 3–5 business days.

Free returns within 30 days.

Accessibility

Full keyboard navigation (arrows, Home, End, Delete), roving tabindex, and correct ARIA roles for both the tablist and accordion disclosure patterns.

Examples:
Keyboard Navigation

Keyboard Navigation

Focus a tab, then use ← → Home End. Press Delete on a removable tab to close it.

Use the arrow keys to move focus between headers.
Home and End jump to the first and last enabled header.
Press Delete while focused to remove this panel.

Side panel

A non-modal panel docked to one edge of its container: it narrows the content (side) or floats over its edge (over) without a backdrop, scroll lock or focus trap, so the page stays usable while it is open.

Examples:
Side panel (non-modal companion)

Side panel (non-modal companion)

Rendered as side
Invoice draft

The page stays usable while the assistant is open: edit the field, scroll, select text.

Consulting — 8 h × €60.00

Design review — 3 h × €75.00

Hosting, September — €19.00

Domain renewal — €12.00

Support plan — €45.00

Travel — €38.50

Training session — 2 h × €60.00

Licence, annual — €240.00

Assistant

Hi! Ask me to fill in any field of this invoice.

Key features

Recent changes

Version 22.17.1 - 9/28/26, 12:00 AM

changed: The family list in both READMEs names `ng-hub-ui-spreadsheet`. The sheet joined the family and the other lists had not caught up. Documentation only: no code, types or styles change.

Version 22.17.0 - 9/24/26, 12:00 AM

fixed: BREAKING (visual): every label the strip derives from its accent is readable. --hub-panels-accent-emphasis was color-mix(accent 80%, ink), and a percentage cannot darken a pale hue: a hovered warning tab measured 2.23:1 on its own hover surface, info 2.73:1 and light 1.56:1. The same applied to --hub-panels-card-color, --hub-panels-alert-color and the card band’s --hub-panels-panel-header-color, so the open path a custom variant goes through shipped broken while the nine built-in variants were served correct tints by the design system — the info card’s band label measured 2.46:1. All four now steer the accent into the theme’s emphasis window, keeping hue and chroma; the nine measure between 6.26:1 and 14.63:1. See BREAKING_CHANGES.md.

changed: BREAKING (visual): the ✕ of a removable tab stops being faded. --hub-panels-remove-btn-opacity rested at 0.6, which is a de-emphasis and not a disabled state — nothing exempts an enabled control with an accessible name from 4.5:1 — and at that strength it measured 2.42:1 on the active tab and 4.18:1 on the others. It defaults to 1 and now reads exactly as contrasty as the tab label it belongs to.

added: Two shape tokens for <hub-side-panel>: --hub-side-panel-border-radius rounds it and --hub-side-panel-inset holds it off the edges of its container. Until now neither was reachable without writing CSS against .hub-side-panel, the internal class the token contract exists to keep private. Both default to 0, so a panel that sets neither paints exactly as before. The radius is one token and not one per corner: which corners it lands on follows position, so an end panel rounds its inline-start corners and a start panel its inline-end ones — the pair facing the content. The pair against the container edge stays square while the panel is flush with it, and rounds with the rest as soon as the inset moves the panel off that edge. The inset is subtracted from the closed position too, so a panel held off the edge still slides fully out of sight.

Version 22.15.0 - 9/23/26, 12:00 AM

fixed: A position: fixed element inside <hub-side-panel-container> measures from the window again. The container declared container-type: inline-size, which applies layout containment, and a layout-contained box becomes the containing block for every fixed descendant. Anything meant to cover the screen from inside the content area — a fullscreen loading overlay, a cookie banner, a consumer's own dialog — was sized and placed against the container instead, so it covered the content column and left the rest of the page untouched. Products were working around it by moving the node to <body> with a directive of their own.

changed: BREAKING: the container is no longer a query container. container-type and container-name are gone from <hub-side-panel-container>, and the panel width cap is a plain 100% instead of 100cqi. The cap resolves against the same box and behaves the same; what changes is that a consumer-written @container hub-side-panel-container (…) query stops matching, and an unsized container now takes its width from its contents. See BREAKING_CHANGES.md.

Version 22.14.0 - 9/23/26, 12:00 AM

changed: BREAKING: the Angular floor rises from 17.2.0 to 17.3.0. The old range was measured from the source alone, and its published `.d.ts` names `InputSignalWithTransform` or `OutputEmitterRef`, which Angular did not ship until 17.3. An application below the new floor could install this package and then fail to build; it now gets the peer warning it should always have had. Nothing that worked stops working. See BREAKING_CHANGES.md.

Version 22.13.0 - 9/23/26, 12:00 AM

fixed: A panel written inside an @if now sits where the template puts it instead of last. The strip was built from panels adding themselves in their own constructor, so the order on screen was the order they happened to be created: a conditional panel is created when its condition turns true, landed after every unconditional one, and went back to the end each time the condition flipped. A condition that was true from the first render produced the wrong order too.

changed: The container discovers its panels with a content query rather than waiting to be told, which reports them in the order the consumer wrote them and reaches through @if and @for. A panel nested inside a component projected into a pane is still not captured as a hidden tab.

changed: BREAKING: panels is a computed signal now, not a writable one, so it has no set or update. Reading it is unaffected. registerPanel() does nothing and is deprecated, kept so older code still compiles and removed in 23.0.0. See BREAKING_CHANGES.md.

added: HUB_PANEL, the token a panel provides itself under, so the container can query its panels without importing the panel class and closing a cycle between the two.

Version 22.12.2 - 9/23/26, 12:00 AM

changed: The Angular peer range now says what the code needs. It asked for >=21.0.0, which nothing in this package justified: the newest Angular API the source uses is model() and the signal queries, which shipped in 17.2, and the compiled output carries no linker marker above it. The range is >=17.2.0, and npm run peers:floors derives and checks that floor from now on.

Version 22.12.1 - 9/16/26, 12:00 AM

changed: Repository, issue and README links follow the move to the hub-env organization. Issues for every Hub UI package are now gathered in hub-env/hub-ui, and the repository and bugs fields of the manifest point at the new addresses. No code, types or styles change.

Version 22.12.0 - 9/13/26, 12:00 AM

added: <hub-side-panel> inside a <hub-side-panel-container>: a side panel that is not a dialog. No backdrop, no scroll lock and no focus trap, so the page stays usable while it is open. mode="side" docks it and narrows the content; mode="over" floats it over the content edge, and a side panel falls back to over when the container is narrower than breakpoint (768 by default). Logical start/end position, a two-way open model, Escape to close, opt-in autoFocus with focus handed back on close, and content that survives closing. Themed through twelve --hub-side-panel-* tokens.

Version 22.11.0 - 9/8/26, 12:00 AM

changed: BREAKING — the seven exported classes are renamed with the Hub prefix: PanelsComponent, PanelComponent, PanelsConfig, PanelHeadingDirective, PanelHeadingActionsDirective, PanelHeaderDirective and PanelFooterDirective become HubPanelsComponent, HubPanelComponent, HubPanelsConfig, HubPanelHeadingDirective, HubPanelHeadingActionsDirective, HubPanelHeaderDirective and HubPanelFooterDirective. PanelComponent is an ordinary name for an ordinary thing, and an application that has a panel of its own will reach for it — an unprefixed export puts the library inside the consumer’s namespace and leaves them aliasing their way out of a collision they did not create. The selectors and the exported types were already prefixed, so the classes were the last part of this surface still spelled the other way. All seven old names stay exported as deprecated aliases resolving to the same classes, so imports, viewChild() lookups and a { provide: PanelsConfig, ... } provider all keep working, and they are removed in 23.0.0. See BREAKING_CHANGES.md.

fixed: Both READMEs name the classes that ship. The import block and every reference to the container, the panel and the config token still used the old names, so a reader copying the documented import wrote code compiling only against the deprecated aliases — which is exactly the audience this rename exists to move.

Version 22.10.4 - 9/6/26, 12:00 AM

added: A BREAKING_CHANGES.md file, written up with the three breaks this library had already shipped without one. The major number states which Angular major is targeted, so it can never be raised to warn of a break, which leaves this file as the only warning a consumer gets, and until now panels had nowhere to give it. It covers the two CSS breaks of 22.8.2 (the disclosure button stopped painting the row surface and stopped being position: relative, both filed as Changed and both released under a patch number) and the 22.3.0 scoping of the PanelsComponent injection to host: true, which stops a wrapper component's own <hub-panel> from joining a group outside its template. Each entry carries the migration the changelog only stated in passing. Documentation only: no code, types or styles change.

fixed: HubPanelAppearance and HubPanelVariant are now re-exported from the package entry point. The 21.3.0 notes announced them as exported types, but the barrel never listed them, so the union behind <hub-panel [appearance]> and [variant] was unreachable from outside the library: consumers had to redeclare it by hand in every project, or widen it to string and lose the very check the types exist for. No runtime change, the surface is type-only.

Version 22.10.3 - 9/1/26, 12:00 AM

changed: The homepage in the manifest points at this library's own documentation page rather than at the site root. It is the link a registry shows beside the package and the one a reader clicks from it, and landing on a front page they then have to search is a worse answer than landing on the reference for the package they were already looking at. Metadata only — no code, no types, no styles change, and nothing a consumer imports is affected.

Version 22.10.2 - 8/17/26, 12:00 AM

fixed: The published package declared no licence. An absent license field is not neutral — a registry reports it as unlicensed, which legally reads as all rights reserved, the most restrictive state possible rather than the most open. The intent was always MIT; it is now stated in package.json and carried in a LICENSE file that ships with the package.

Version 22.10.1 - 8/8/26, 12:00 AM

fixed: Documentation links now point at the canonical localized URLs. The README linked to https://hubui.dev/<path> with no locale prefix and no trailing slash, and both forms are 301-redirected, so every reader arriving from npm or GitHub landed on a redirect instead of the canonical page.

Version 22.10.0 - 7/28/26, 12:00 AM

changed: Accent resolution now imports the canonical resolveHubAccent from ng-hub-ui-utils; the private in-library copy was deleted. Behaviour is identical.

added: New required peer dependency: ng-hub-ui-utils >=22.7.0 (home of resolveHubAccent). ng add ng-hub-ui installs it automatically; manual installs need npm i ng-hub-ui-utils.

Version 22.9.0 - 7/28/26, 12:00 AM

added: New removeLabel input on <hub-panel> (default "Remove panel"): the accessible name announced for the ✕ remove button, overridable per panel so consumers can localize it.

fixed: The ✕ remove control was an aria-hidden <span> nested inside the tab / disclosure <button> — unfocusable, unannounced and unreachable by keyboard. It is now a real <button type="button"> with an aria-label, rendered as a SIBLING of the header control (the slot pattern hubPanelHeadingActions introduced): beside the tab button in the strip views, inside the accordion actions area at the row end. Clicking it still removes without toggling or selecting, it is disabled with its panel, and the .hub-panels__remove-btn class and its opacity tokens are unchanged.

fixed: Removing a panel through the ✕ now returns keyboard focus to the closest remaining header (as the Delete key already did) instead of dropping it on <body>. New public removePanelAndRefocus(panel) method on PanelsComponent, shared by the Delete-key handlers.

Version 22.8.3 - 7/26/26, 12:00 AM

fixed: Declared the real ng-hub-ui-ds peer range (>=22.0.0); the previous >=1.0.0 floor predates the --hub-ref-*/--hub-sys-* token architecture.

Version 22.8.2 - 7/9/26, 12:00 AM

fixed: The hairline closing an expanded accordion row stopped short of the row's end. It was an inset box-shadow on the disclosure button, which stopped spanning the whole header when 22.8.0 made it flex: 1 1 auto to free space for hubPanelHeadingActions. It is now drawn on .hub-panels__accordion-header--expanded, which always spans the full row.

fixed: togglePosition="end" put the chevron at the end of the BUTTON rather than of the row, so with hubPanelHeadingActions it landed between the heading and the affordances. The trailing chevron is now anchored to the header’s inline end, and whatever sits last in the row reserves its gutter through the new --hub-panels-accordion-toggle-gutter property.

changed: BREAKING (CSS contract): .hub-panels__accordion-btn no longer paints the row surface — it is transparent and the header’s surface shows through, which is what lets the inset hairline reach the row’s end. The --hub-panels-accordion-btn-bg and --hub-panels-accordion-active-bg tokens still drive the colour, now read by the header. A consumer who set background directly on .hub-panels__accordion-btn must move it to .hub-panels__accordion-header. See BREAKING_CHANGES.md.

changed: BREAKING (CSS contract): .hub-panels__accordion-btn is no longer position: relative; the header carries it instead, so the trailing chevron can be anchored to the row. A consumer who relied on the button as the containing block for absolutely positioned heading content must now give that content its own containing block. See BREAKING_CHANGES.md.

Version 22.8.1 - 7/9/26, 12:00 AM

fixed: togglePosition leaked into a nested accordion: the chevron side was a class on the container plus a descendant selector, so an accordion nested inside a panel body inherited the outer placement and could not choose its own. The flag now rides each <hub-panel> and the rule is scoped to that panel's own header.

Version 22.8.0 - 7/9/26, 12:00 AM

added: New hubPanelHeadingActions directive: a header-row slot for accordion affordances that must stay usable while the row is collapsed (edit, delete, a menu). It renders as a sibling of the disclosure button, so its controls are real controls, a click never toggles the panel and tab order follows the DOM. hubPanelHeading projects INSIDE that button (a nested <button> is invalid and unreachable by keyboard) and hubPanelHeader projects inside the collapse region (it vanishes when the row closes), so until now there was nowhere to put them.

added: New togglePosition input on <hub-panels> (start | end, default end; also on PanelsConfig): which side of the accordion header row the disclosure chevron sits on. Only the visual order moves — the DOM is untouched — and the offsets are logical, so start is the left edge in LTR and the right edge in RTL. Replaces the ::ng-deep order/margin override consumers were writing by hand. Exports the HubPanelsTogglePosition type.

changed: .hub-panels__accordion-header is now a flex row carrying the row surface (with a new --expanded modifier), the disclosure button being its flex child, so the actions slot shares the background in both states. The button’s own background rules are unchanged.

fixed: RTL in the accordion header: the disclosure button used the physical text-align: left and its chevron margin-left: auto, so under dir="rtl" the label stayed left-aligned and the chevron pinned to the wrong edge. Both are logical now.

Version 22.7.0 - 7/8/26, 12:00 AM

added: New --hub-panels-tab-border-end-radius token: the radius of a vertical tab’s corners on the edge that meets the panel body. They used to be hardcoded to 0, so the active tab always docked flush into the panel. Defaults to 0 (no visual change); raise it to render fully-rounded, standalone rail items such as a routed settings/profile subnav.

Version 22.6.0 - 7/7/26, 12:00 AM

added: New hub-panels-theme(...) SCSS mixin: one-call token theming for <hub-panels>. $accent feeds the single --hub-panels-accent slot (the component derives the emphasis/subtle/on family), alongside the panel surface and the tab appearance. Every parameter is null-defaulted and additive; for any token not exposed, set the --hub-panels-* custom property directly.

changed: Packaging: the library now ships its SCSS at /styles, exposing hub-panels-theme as a first-class package entry (@use 'ng-hub-ui-panels/styles' as *;).

Version 22.5.0 - 7/7/26, 12:00 AM

changed: variant on <hub-panel> / <hub-panels> now accepts ANY colour: on top of the built-in semantic accents it takes a registered custom accent or a literal colour (#ff0000, rgb(...), oklch(...), a CSS named colour), resolved through the shared resolveHubAccent helper. The single accent slot derives the rest of the family, so built-in colours are unchanged.

changed: Internal: @HostBinding / @HostListener decorators replaced by the host metadata object (Angular style guide). No public API or behaviour change.

Version 22.4.0 - 7/5/26, 12:00 AM

added: New <hub-tab-nav> — a lightweight, content-less, value-bound tab strip (role=tablist) that emits the selected value via a two-way active model, for segmented controls / filter switches / manual tabs-with-external-content. Supports tabs/pills appearances, justified/vertical layouts and roving-tabindex keyboard nav.

added: Card panels now honour [variant]: a plain card reflects data-variant and tints its bg/text/border from a single inline accent (--hub-panels-card-accent) with the same open color-mix + ds-tint model as the alert. New --hub-panels-card-border-style (e.g. dashed empty-state cards).

added: New <hub-panel flush> input (zero card body padding) and <hub-panel fill> input (fill-height card with a scrolling body; the wrappers gain stable classes and --hub-panels-body-gap spaces stacked children).

added: New <hub-panel standalone> static attribute: opts a loose panel out of an ancestor tabs/pills/accordion group in the same template so it renders as a plain card (the case host: true cannot cover).

changed: --hub-panels-card-box-shadow and --hub-panels-panel-header-bg are now inheritable (default moved to the usage-site var() fallback) so an ancestor can set them; identical default, no visual change.

Version 22.3.0 - 7/2/26, 12:00 AM

fixed: A standalone <hub-panel> nested (transitively) inside a tab pane no longer registers as a hidden tab of the outer <hub-panels>: the group injection is now bounded with host: true, so a panel binds only to a group declared in the same template and renders as a card/alert otherwise. The providers: [{ provide: PanelsComponent, useValue: null }] workaround is no longer needed.

changed: Canonical accent-slot derivation: the group role family (emphasis/subtle/on) now derives locally from the single --hub-panels-accent slot with the design-system formulas, so custom accents re-derive the full family at runtime; built-in variants only re-base the accent slot.

changed: The alert open-set default (appearance="alert" with a custom variant) uses the same canonical tints as the built-ins (12% background over surface, 80% text over ink).

Version 22.2.0 - 6/26/26, 12:00 AM

added: Open-set accent variants: <hub-panels variant="…"> and <hub-panel appearance="alert" variant="…"> ship all 9 built-ins (primary…dark) with exact ds tints, and any custom string keeps working at runtime by defining a single --hub-sys-color-<name>.

Version 22.1.0 - 6/22/26, 12:00 AM

added: New variant input on <hub-panels> selecting the semantic accent of the navigation strip (active/hover tab, active pill, active accordion header). Built-in variants (primary | success | danger | warning | info) use the exact design-system tints; any custom string is also accepted and derived with color-mix. Defaults to primary.

added: New group-accent tokens --hub-panels-accent, --hub-panels-accent-emphasis and --hub-panels-accent-subtle; the strip active/hover affordances now resolve through this single accent instead of being hard-wired to --hub-sys-color-primary*.

changed: The outer container chrome now inherits from the --hub-container-* base layer (re-base hook), so overriding a container token on a subtree re-bases the panels chrome. No visual change with default tokens.

Version 22.1.1 - 6/25/26, 12:00 AM

fixed: Design-token consistency pass: inline fallback defaults now match the canonical ng-hub-ui-ds values, and the hardcoded literals left behind (z-index, font-weight, line-height, radii and theme-aware colours) go through their --hub-sys-* / --hub-ref-* tokens, so they follow the active theme. No visual change when the ds tokens are loaded.

Version 22.0.0 - 6/17/26, 12:00 AM

changed: Aligned with Angular 22. The major number of this package states the Angular major it targets, so this release is a compatibility statement rather than an API break.

changed: README documentation standardized.

Version 21.3.0 - 6/16/26, 12:00 AM

added: New alert appearance for a standalone <hub-panel>: appearance="alert" with a variant renders a semantic callout (role="alert").

added: New variant input (primary | success | danger | warning | info) selecting the alert colour; omit it for a neutral alert. Exported HubPanelAppearance and HubPanelVariant types.

added: New alert tokens (--hub-panels-alert-*); each variant re-points them at the --hub-sys-color-<variant>-{subtle,border-subtle,emphasis} family, so the alert inherits every theme and dark mode automatically.

Version 21.2.0 - 6/14/26, 12:00 AM

added: New card visualization (type="card"): a chromeless format with no navigation strip where every <hub-panel> is always visible and styled as a card.

added: A <hub-panel> can now be used standalone, outside any <hub-panels> container, rendering as a card on its own.

added: New content-slot directives hubPanelHeader and hubPanelFooter, rendering header/footer bands in every view, distinct from the navigational hubPanelHeading.

Version 21.1.1 - 6/12/26, 12:00 AM

added: New token: --hub-panels-header-bg, used by the tabs/pills strip background while defaulting to the panel surface.

added: New token: --hub-panels-pill-content-border-width, which controls the bordered card chrome in the pills content area.

changed: The active header background now defaults to --hub-panels-content-bg so the active tab/panel fusion stays aligned when themes retint the content surface.

changed: The pills content area is borderless by default; themes can opt back into a bordered card by overriding --hub-panels-pill-content-border-width.

Version 21.1.0 - 6/11/26, 12:00 AM

added: Multiple selection in the tabs/pills views: each open pane becomes its own bordered box placed next to the others (side by side when horizontal, stacked when vertical), with a per-pane minimum and scroll on overflow.

added: New tokens: --hub-panels-pane-min-width, --hub-panels-pane-min-height, --hub-panels-pane-gap, --hub-panels-nav-content-gap, --hub-panels-pill-gap.

fixed: Accordion content was always empty (two unselected ng-content slots); replaced with a single projection slot.

fixed: tabs and vertical tabs now render as a single bordered box around the strip and content together (not two boxes); pills gain spacing before a bordered content card.

changed: Smooth strip scrolling, not-allowed cursor on disabled headers, and full-width container.

Version 21.0.0 - 6/11/26, 12:00 AM

added: Initial release of ng-hub-ui-panels with tabs, pills and accordion visualizations.

added: ControlValueAccessor integration with single and multiple selection, bindValue and compareWith.

added: Routed panels via routerLink with route/full pathMatch and a <router-outlet> content area.

added: Keyboard navigation (arrows/Home/End/Delete), scrollable, vertical and justified strips.

added: Accordion view with multiple expansion, flush layout and an animated grid-based collapse.

added: Custom header templates through the hubPanelHeading directive and full --hub-panels-* theming.

changed: Supersedes ng-hub-ui-accordion; the accordion view honours the --hub-accordion-* token contract for theme compatibility.

Frequently asked questions

How do I build tabs, pills or an accordion in Angular with one component?

Import HubPanelsComponent and HubPanelComponent and write <hub-panels type="accordion"> with one <hub-panel heading="…"> per section. type takes tabs, pills, accordion or card, and changing it swaps the whole pattern without touching the markup. multiple lets more than one section stay open in accordion mode, vertical turns the strip on its side, scrollable keeps a long strip on one line, and panelChange tells you which panel became active.

Can a panel follow the router or bind to a reactive form?

Both. Give a panel a routerLink, with queryParams and pathMatch if you need them, and the content area becomes a router outlet that follows the URL. The container implements ControlValueAccessor, so <hub-panels formControlName="section"> binds the active panel to a form control; bindValue picks which property travels as the value and compareWith decides identity. On a single panel active is a model(), so [(active)] works for the simple case.

Are the tabs and the accordion keyboard accessible?

Yes, and the roles are real: role=tablist and role=tab with a roving tabindex and aria-selected, each panel a tabpanel labelled by its tab. Arrow keys move between panels — up and down only when vertical is set — Home and End jump to the ends, and Delete removes a panel marked removable and moves focus to the next one. In accordion mode every heading is a native button with aria-expanded, and a collapsed region is inert rather than merely hidden, so nothing inside it takes focus.

Is ng-hub-ui-panels a drop-in replacement for ng-hub-ui-accordion?

For the accordion view, yes: <hub-accordion> becomes <hub-panels type="accordion">, <hub-accordion-panel title> becomes <hub-panel heading>, and every --hub-accordion-* variable you already set still applies, because each accordion token reads it before falling back. Two conditions attached. Those variables reach the accordion view only, not tabs, pills or cards. And panels asks for Angular 21, ng-hub-ui-utils and @angular/router, which it injects whether or not you use a routed panel, so an application without provideRouter fails when the component is created.