fold-ng
v0.9.0
Published
Accessible, dark-first Angular 22 UI component library & design system — signals-first, standalone, zoneless, SSR-ready. Design tokens + WCAG-minded components, themeable to the bone.
Downloads
1,848
Maintainers
Readme
fold-ng
fold-ng is an accessible, dark-first Angular 22 UI component library and
design system — signals-first, standalone, zoneless, and SSR-ready. It ships a
two-tier design-token model (themeable to the bone) plus WCAG-minded
components: buttons, forms, overlays/dialogs, navigation, data tables, toasts and
more. No NgModule, no zone.js, no runtime CSS-in-JS — just standalone
components styled against CSS variables.
▶ Live demo & component gallery
Every component, driven by a live playground — the fastest way to see what
fold-ng looks and behaves like.
Production-quality, pre-1.0 (
0.x). Every component is tested, the package builds AOT green, and it's dogfooded as the design system of a real application. It stays0.xonly because the API isn't frozen until1.0.0— not because it's unstable. Pin your version;0.xminor bumps may still refine the API. SeeCHANGELOG.mdfor the road to 1.0.
Install
npm install fold-ngAngular 22 (@angular/core, @angular/common, @angular/forms,
@angular/platform-browser) is a peer dependency.
Bundle size. Components are standalone and the package is side-effect-free except its CSS (
sideEffects: ["**/*.css"]), so you only ship what you import — not the whole library. (No Bundlephobia badge: it can't measure an Angular partial-Ivy package, which needs the Angular linker to become final JS — the install-size badge above is the honest figure.)
Quickstart
Import the tokens once, then use any standalone component directly:
// styles.css
@import "fold-ng/tokens.css";import { Component } from "@angular/core";
import { FoldButtonComponent, FoldCardComponent } from "fold-ng";
@Component({
standalone: true,
imports: [FoldButtonComponent, FoldCardComponent],
template: `
<fold-card>
<button foldButton emphasis="solid" (click)="save()">Save</button>
<a foldButton emphasis="outline" intent="neutral" routerLink="/back">
Cancel
</a>
</fold-card>
`,
})
export class DemoComponent {
save() {}
}Everything is dark by default; opt into light with data-theme="lumen". See
docs/TODO.md for the roadmap and the full component list below.
Consuming the tokens
Import the CSS once at your app's style entry point:
@import "fold-ng/tokens.css";Everything renders umbra (the dark theme) by default. To switch a subtree (or the whole app),
set data-theme on an ancestor — usually <html>:
<html data-theme="lumen"></html>Five themes ship: umbra (the dark base, no attribute), lumen (light),
bubbly (festive lavender, violet brand, rounded), navi (dark chrome, light
page) and titan (brushed titanium — a light brushed-steel ground with a
frameless top and floating rails, bright cards, a heat-anodized copper-orange
brand, solid steel borders, iPhone-soft corners). The extras exist to prove the point — each
is the umbra or lumen block with its primitive families re-pointed. Adding a
sixth is a new [data-theme] block in semantic.css plus the primitives it
names.
bubbly and navi also change their corners — radius is the one scale a theme may
re-declare, because corner softness is a brand axis (friendly vs
institutional) and it is the only scale that changes nothing about where a box
sits. Type, space, motion and elevation stay theme-invariant: retheming must
never re-flow a page. The contract test enforces that split.
navi is the interesting one: mixing chrome and page means one theme needs the
text/border/surface roles to differ per region, which a single set of
roles cannot express. It gets there by re-declaring those roles on
[data-surface="chrome"] — a contract any element opts into with the
foldSurface directive (the app-shell stamps it on its rails + header) — so the
theme never names a component's internals. Variables only. If mixed chrome ever
stops being a demo, the catalogue should grow real *-on-chrome roles.
Auto-inverting surfaces
The same [data-surface] seam powers an accent surface — a region filled
with the brand accent whose entire content sub-tree flips to a compatible
on-accent palette with no per-component code. Drop a fold-card in a grid
and set surface="accent" (or stamp foldSurface="accent" on any element):
<fold-card surface="accent">
<h3>Studio plan</h3>
<p>Everything in Pro, plus shared workspaces.</p>
<fold-badge content="Popular" variant="accent" />
<fold-link href="/plans">Compare plans</fold-link>
<button foldButton>Choose</button>
</fold-card>Every nested thing — the text ramp, the hairline border, a raised band, the badge, the link, the button, even a filled icon tile — reads correctly on the accent. Not because the card special-cases them, but because the region re-points the semantic roles they already resolve against.
How it works (and why it survives a solid button)
An inverting surface has to do something a chrome surface never does: swap the
brand pair. On the accent, --fold-color-primary should become the light ink
(so a link or a filled tile's ground reads), and --fold-color-on-primary should
become the accent itself (so the label on that tile reads). Written naively
that's a CSS custom-property cycle (a: var(b); b: var(a) → both invalid).
The trick: capture on the surface, invert on the descendants. The surface element records the accent and its ink into two private vars while the tokens still hold their normal values, and the inverted role-set is applied to descendants from those captures — so a role can reference the pre-inversion value it is replacing, no cycle:
[data-surface="accent"] {
--_accent-ink: var(--fold-color-on-primary); /* captured here… */
--_accent-fill: var(--fold-color-primary);
}
[data-surface="accent"] * {
--fold-color-text: var(--_accent-ink); /* …consumed here */
--fold-color-primary: var(--_accent-ink); /* fill → light ink */
--fold-color-on-primary: var(--_accent-fill); /* on-fill → the accent */
/* …surfaces/borders as color-mix of the captured pair… */
}Every value is a color-mix of the captured pair, so it is derived, not
authored — one definition holds on all five themes, and the surface/band steps
stay a lighter shade of the accent (gradation kept in-hue).
Overriding per theme
None of this is a cage. Writing your own CSS in a card is always free — raw values don't reference the roles, so a surface never touches them. An override isn't a hack: it re-anchors one relationship (text ↔ ground ↔ accent) inside a controlled frame, and coherence follows because the ratios are preserved. Speak in roles and gain the adaptation, or paint a pixel and own it — both coexist.
The derived defaults are good, not sacred. A theme that wants a different on-accent ramp (a light accent might want darker text, say) overrides any role by nesting its own theme selector under the surface — the same seam chrome uses:
[data-theme="titan"] [data-surface="accent"] * {
--fold-color-text: var(--fold-ref-steel-900); /* dark ink on a light accent */
}The one honest limit: an accent surface is a single ground, so surface
stays one axis — there is no accent × sunken. That is a feature (a card in a
grid has one job: stand out), not a gap.
Then style against the semantic tokens — never a raw colour:
.header {
background: var(--fold-color-bg-header);
}
.cta {
background: var(--fold-color-primary);
color: var(--fold-color-on-primary);
}
.cta:hover {
background: var(--fold-color-primary-strong);
}From TypeScript you get the same tokens, typed:
import { foldColorVar } from "fold-ng";
el.style.background = foldColorVar("bg-page"); // "var(--fold-color-bg-page)"
foldColorVar("bg-pag"); // ✗ compile error — misspelt tokenThe two-tier model
Tokens come in two layers. This separation is the whole point — it is what lets another project re-theme by swapping the palette, and what keeps the app from hard-coding colours.
| Tier | File | Prefix | Role |
| ------------------ | ---------------- | ---------------- | --------------------------------------------------------------------------- |
| 1 · Primitives | primitives.css | --fold-ref-* | The raw palette. The only place a literal hex is allowed. Theme-invariant. |
| 2 · Semantic | semantic.css | --fold-color-* | Role tokens (bg-header, primary…). Point at primitives. Flip per theme. |
Components consume tier 2 only. A component never names --fold-ref-teal-500;
it names --fold-color-primary. Re-theming means re-pointing the semantic layer,
never touching a component.
The naming convention
--fold-<tier>-<category>-<role>[-<variant>]
│ │ │ └─ strong · primary · secondary · tertiary …
│ │ └────────── page · header · rail · primary …
│ └───────────────────── color · (space, radius, text … to come)
└───────────────────────────── ref (primitive) · color (semantic)Namespaced with --fold- so the package never collides with an app's own tokens.
The bg- rule. A surface fill role carries a bg- marker (bg-page,
bg-rail-primary); a foreground/brand role does not (primary, on-primary).
So bg- reads as "this paints a background."
The contract test
src/tokens/__tests__/tokens.contract.spec.ts is the lock. It fails the build
if:
- any theme block falls out of parity with the catalogue (the dark
:rootbase and every[data-theme]override are checked); - a semantic token points at a primitive that doesn't exist (dangling
var); - a semantic token hard-codes a hex instead of referencing a primitive;
- a primitive is declared but never used;
- the CSS drifts from the typed catalogue in
tokens.catalog.ts.
Add a token → add it to tokens.catalog.ts and every theme block, or the
test goes red. That is how a theme stays complete.
pnpm --filter fold-ng testCurrent token set
Deliberately small — we grow it together, one confirmed role at a time.
| Semantic token | Role |
| -------------------------------- | ----------------------------- |
| --fold-color-bg-page | Page background |
| --fold-color-bg-header | Top header bar |
| --fold-color-bg-rail-primary | Rail 1 — app menu |
| --fold-color-bg-rail-secondary | Rail 2 — workspace menu |
| --fold-color-bg-rail-tertiary | Rail 3 — tertiary nav |
| --fold-color-primary | Primary / accent (brand teal) |
| --fold-color-primary-strong | Primary hover / active |
| --fold-color-on-primary | Text / icon on a primary fill |
(plus the status families, neutral surfaces, the two card tints surface-card /
surface-sunken, glass, and the
radius / text / icon-size / space / motion / blur scales — see
tokens.catalog.ts for the full, typed set.)
Components
All standalone, signals-first, styled against the semantic tokens. Import from the package root.
Find one by what you need
The reference table below is keyed by component name — but you usually know
your intent, not the name (that's how an uppercase mini-title gets hand-rolled
instead of reaching for fold-element-title). Start here; before hand-rolling a
label, field, badge, card or overlay, scan this table — fold almost certainly
ships it.
| I need to… | Reach for |
| ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| a small uppercase label / eyebrow over a group | fold-element-title (variant="eyebrow" · bar · title) |
| a section with a title + description + actions | fold-page-section (semantic <section> + aria-labelledby) |
| show read-only label/value pairs (a recap) | fold-field-list / fold-field (dl/dt/dd) |
| a text field | fold-input — number → fold-number-input · multiline note → fold-textarea |
| a date / time field | fold-date (date·datetime-local·month·week) · fold-time |
| a dropdown | fold-select (native) — custom rows → fold-listbox · multi → fold-multiselect |
| an on/off field · a password field | fold-checkbox · fold-password-field |
| a range / debounced search | fold-slider / fold-range-slider · fold-search |
| a button · icon-only action · icon on/off | fold-button (<button>/<a>) · fold-button-icon · fold-toggle-icon · text link → fold-link |
| an inline "are you sure?" guard | fold-inline-confirm (no modal — simple · type-to-confirm · secret) |
| a "delete X" danger block (type-to-confirm) | fold-danger-zone (alert frame + blast-radius text + retype-to-arm) |
| a status / count pill · status→colour | fold-badge · fold-status-badge |
| a tinted message / alert row | fold-callout (inset for in-flow) |
| a transient toast | fold-toast + FoldToastService |
| a card · titled info card · page splash | fold-card · fold-context-card · fold-hero-section (bordered → fold-hero-card) |
| in-page tabs · a routed nav bar · a segmented control | fold-tabs · fold-view-nav · fold-view-toggle / fold-choice-row |
| a breadcrumb trail (routerLink or href) | fold-breadcrumb ([items], last = current page) |
| a "← Back" link (route, href, or history) | fold-back-link (routerLink / href / history-back button) |
| a table + pagination | fold-data-table · fold-paginator |
| an avatar · a cluster | fold-avatar (+ …Detail) · fold-avatar-list |
| an empty / loading state | fold-empty-state · fold-loading / fold-spinner |
| a side panel · anchored popover · actions menu | fold-panel-host · fold-popover · fold-dropdown |
| a collapsible section | fold-disclosure (a modal dialog is roadmap — use a modal fold-panel-host or fold-inline-confirm) |
| an icon · a calendar · a timeline / stepper | fold-icon · fold-calendar-month/-week/-day/-list/-agenda/-timegrid · fold-timeline |
| a nav rail · app skeleton · a detail page with rails | fold-menu · fold-app-shell · fold-aside-layout (page scaffold → fold-page-layout) |
| drag-drop file upload | fold-file-dropzone |
| Component | Selector | What it is |
| -------------------------------------------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| FoldAppShellComponent | fold-app-shell | Responsive app skeleton (rails + header + content + self-collapsing footer slots; headerLayout/footerLayout inset·full, footerBehavior pinned·scroll; mobileNav drawer·none — [(mobileNavOpen)] off-canvas drawer for the primary rail on mobile, or none to compose an fold-nav-launcher; scroll scroll·stage — the shell owns the content scroll by default so pages flow ([foldScrollRegion] opts a nested area back into its own scroll); built-in skip-link to a focusable <main>. Regions float per-surface via foldElevated, not a shell flag). |
| FoldMenuComponent (+ Item / Section / Separator) | fold-menu | Collapsible nav rail — coloured sections, tint="follow", depth level, collapse-toggle placement. Items are a[fold-menu-item]. |
| FoldNavLauncherComponent (+ FoldNavTileComponent) | fold-nav-launcher | Full-screen mobile nav launcher — a centred tile grid over a blurred scrim (scrim / Escape / close dismissal, focus-trap, scroll-lock). columns="auto" scales tiles to the count. Pairs with fold-app-shell mobileNav="none". Tiles are a[fold-nav-tile] — variant="surface"·filled. |
| FoldPageLayoutComponent | fold-page-layout | Page scaffold — gutter + header + body rhythm; fills its container (width is a content concern). Tokens --fold-page-gutter / --fold-page-gap; sections can bleed edge-to-edge. |
| FoldPageSectionComponent | fold-page-section | Semantic <section> grouping — eyebrow title (names the region via aria-labelledby) + description + actions; stack / bleed helpers. Not a box — compose a fold-card inside for that. |
| FoldHeroSectionComponent | fold-hero-section | Full-bleed page splash — the borderless intro band at the top of a page (carries the <h1>). Direct child of fold-page-layout: cancels the gutter + top pad to sit flush, brand-tinted wash + hairline. align center·start, wash, [heroBackdrop] decorative lane. (For a bordered header card, see fold-hero-card.) |
| FoldAsideLayoutComponent | fold-aside-layout | Detail-page grid — a centred column flanked by up to two sticky rails ([asideLeft] / [asideRight]), collapsing to one column on its own container width (:has()-driven, container queries). Labelled rails become complementary landmarks; every track is a CSS var. |
| FoldNavLayoutComponent | fold-nav-layout | Places a bar ([tabNav] — a fold-view-nav or a fold-tabs) with its content — placement="top" or a side rail that folds back on top (hysteretic, on its own width) below foldAt. exportAs="foldNavLayout" exposes stacked() so the projected bar follows in one binding. |
| FoldCardComponent | fold-card | Raised content surface (surface = card/sunken/accent, hairline border, consistent radius). accent is an auto-inverting accent-filled card: the whole content sub-tree re-points to a compatible on-accent palette (text, borders, band gradation, nested buttons/links/icon-tiles) — every value a color-mix of the accent, so it holds on all themes (see auto-inverting surfaces). Optional projected [cardHeader]/[cardFooter] bands with per-band chrome (separators/raisedBands = none/header/footer/both); the body padding never shifts when a band toggles. interactive makes the whole card an accessible button (role/tabindex, focus ring, Enter/Space/click → (activated)). |
| FoldContextCardComponent | fold-context-card | Titled info card: icon header + body + optional footer action. |
| FoldHeroCardComponent | fold-hero-card | Prominent header card — bordered surface × accent overlay + optional accent bar. (For a full-bleed page splash, see fold-hero-section.) |
| FoldElementTitleComponent | fold-element-title | Uppercase section/card mini-title (eyebrow · bar variants). |
| FoldFieldListComponent / …Field | fold-field-list | Read-only dl/dt/dd recap — label/value pairs ([empty] placeholder). The display half of a record; fold-input is the edit half. |
| FoldInputComponent | fold-input | Text-input control (value: string) — Signal Forms ([formField]) or standalone [(value)]; size × align × variant, label / required / hint. The edit half of a record (fold-field reads). |
| FoldNumberInputComponent | fold-number-input | Numeric sibling of fold-input (value: number \| null, empty ⇒ null); owns min / max / step + label / required / hint. Split so each control keeps its true type. |
| FoldTextareaComponent | fold-textarea | Multiline sibling of fold-input (value: string). No resize handle — fixed rows height, wraps + scrolls overflow. Shares the box + label/required/hint/error chrome. Signal Forms or [(value)]. |
| FoldSelectComponent | fold-select | Native <select> wrapper (options projected as <option>); shares fold-input's box chrome. Signal Forms (FormValueControl<string>) or [(value)]. For custom rows use fold-listbox. |
| FoldDateComponent | fold-date | Native calendar-date wrapper (type = date · datetime-local · month · week) — keeps the OS picker, hands back a typed [(value)] string (YYYY-MM-DD). min/max/step pass through. Time-of-day → fold-time. |
| FoldTimeComponent | fold-time | Native time-of-day wrapper (<input type="time">) — typed [(value)] string (HH:mm), min/max/step. The sibling of fold-date. |
| FoldCheckboxComponent | fold-checkbox | Boolean control — a native <input type="checkbox"> (keyboard, indeterminate, forms) restyled to tokens. Signal Forms ([formField], a FormCheckboxControl) or standalone [(checked)]; indeterminate, label/ariaLabel, hint/errors, size. |
| FoldPasswordFieldComponent | fold-password-field | Password input + a live requirements checklist (a dot/tick per rule). Rules injected via FoldPasswordRule ({ label, test } — regex/zod/anything); revealable eye (a fold-input capability); marker dot/check; [rules] slot to redesign the list; validChange; Signal Forms. |
| FoldViewToggleComponent | fold-view-toggle | Segmented single-select (Cards/Table, density, chart-mode…). Generic options ({ value, icon?, label?, ariaLabel?, disabled? }) + [(value)]; a real role="radiogroup" — roving tabindex, arrow keys, Home/End, disabled-skip; size, iconOnly, activeStyle (raised / accent). |
| FoldSearchComponent | fold-search | Debounced search box — an fold-input that emits searchChange once typing settles (delayMs), trimmed + de-duplicated. |
| FoldListboxComponent / FoldOptionComponent | fold-listbox / fold-option | Styleable single-select — the richer sibling of fold-select (native <select>) for options that need custom rows (icon, second line, status). On fold-popover; role="listbox" + aria-activedescendant, full keyboard (↑/↓, Home/End, type-ahead, Enter). Signal Forms (FormValueControl<string>, [formField]/[(value)]); shares fold-input's box chrome. |
| FoldMultiselectComponent | fold-multiselect | Multi-select sibling of fold-listbox (same popover + fold-option rows). Value is a set (readonly string[]); activating a row toggles it and the panel stays open. Separate component — not a multiple flag — so the Signal-Forms value type stays honest. role="listbox" + aria-multiselectable; the trigger summarises the picks. |
| FoldOptgroupComponent | fold-optgroup | Labelled group of <fold-option>s inside a fold-listbox / fold-multiselect — the styleable <optgroup>. Presentational: role="group" + aria-labelledby header (no role="option", so keyboard nav skips it); the owner discovers grouped options in document order, so roving crosses groups seamlessly. |
| FoldSliderComponent | fold-slider | Single-value range slider — a styled native <input type="range"> (design-system track/fill/thumb). Signal Forms ([formField], a FormValueControl<number>) or [(value)]; real <label for>, aria-valuetext, hint/errors, focus ring. |
| FoldRangeSliderComponent | fold-range-slider | Dual-thumb range slider selecting a { min, max } window (shares the slider track/thumb). Two-way [(value)]; a labelled role="group", per-thumb i18n aria (minLabel/maxLabel) + formatted aria-valuetext, disabled. |
| FoldFileDropzoneComponent | fold-file-dropzone | File-picker dropzone — drag-over visuals, keyboard activation, hidden <input type=file> plumbing; emits the picked File[] (presentational — never uploads). |
| FoldLinkComponent | fold-link | Inline text link / link-button (icons, accent · muted). |
| FoldButtonComponent | button[foldButton] · a[foldButton] | Action button — applied to a real <button> or <a> (link that looks like a button, gets href/routerLink); orthogonal emphasis (solid·soft·outline) × intent (primary·neutral·warning·danger) × 3 sizes × shape/block; icon/iconTrailing shorthand (auto-sized) or project content; loading (spinner + aria-busy). Use native (click). |
| FoldButtonIconComponent | fold-button-icon | Icon-only momentary button — shape × size × tone; a one-shot action (no pressed state). For a text button use fold-button; for on/off use fold-toggle-icon. |
| FoldToggleIconComponent | fold-toggle-icon | Icon-only toggle — the same surface as fold-button-icon, plus [(active)] + aria-pressed (true/false) and a pressed state. Emits toggled. |
| FoldInlineConfirmComponent | fold-inline-confirm | In-place “are you sure?” guard — the projected trigger swaps to a confirm/cancel row (no modal). Simple (confirmed emits ""), type-to-confirm ([match]), or secret (password, masked, emits the value). confirmIcon + a chosen cancelIcon; Escape cancels; message announced via aria-describedby; focus in-then-back; controlled [(open)] + keepOpenOnConfirm for async pending; i18n via provideFoldInlineConfirmLabels. |
| FoldDangerZoneComponent | fold-danger-zone | Framed destructive-action block for “delete X” settings. appearance="filled" (alert-tinted) or "section" (a danger section: alert border + normal-background body for ordinary content). The confirm reveals on click — an actionLabel button opens an in-place fold-inline-confirm (plain “are you sure?”, or type-to-confirm when confirmPhrase is set); (confirmed) emits the typed text. Omit actionLabel for a section with no action. role="group" + aria-labelledby. |
| FoldDataTableComponent | fold-data-table | Controlled roster table — sortable sticky header, tone rows, controlled selection (checkbox column), roving-keyboard nav, mobileLayout (scroll / auto-cards / custom foldRowCard), an optional foldToolbar bar, sticky-first, density. |
| FoldPaginatorComponent | fold-paginator | Server-side paginator (size selector + range + page nav). |
| FoldTimelineComponent | fold-timeline | Connected rail of nodes (dot + optional date + label) — vertical navigable history or horizontal step progress; nodes optionally clickable. |
| FoldBadgeComponent | fold-badge | Status / count pill (accent/info/warning/alert/success). |
| FoldStatusBadgeComponent | fold-status-badge | Status→colour badge (maps a domain status key to a tone). |
| FoldChoiceRowComponent | fold-choice-row | Segmented / chip selector. |
| FoldViewNavComponent | fold-view-nav | Navigation bar styled as tabs. Items carry a link (routerLink → a real <a>: cmd-click, deep-links, active state auto), an href, or nothing (a button); aria-current="page" on the active one. direction="auto" follows a wrapping fold-nav-layout; collapsed for an icon rail. For in-page panel switching use fold-tabs instead. |
| FoldBreadcrumbComponent | fold-breadcrumb | Hierarchical link trail. Data-driven [items] where each crumb links by routerLink or href (works without the router; RouterLink only instantiates on a routerLink crumb). Last item is the current page (aria-current="page"), not a link. navigation landmark, decorative chevron separators. Needs @angular/router only when a crumb uses routerLink (optional peer). |
| FoldBackLinkComponent | fold-back-link | The “← Back” affordance for a detail page. Three modes by input: routerLink (in-app), href (external / non-router), or neither → a history-back <button> (Location.back()). Router-coupled but degradable — the history mode needs no router. label + leading icon (default chevron-left). |
| FoldTabsComponent + FoldTabPanelComponent | fold-tabs + fold-tab-panel | The in-page ARIA Tabs widget: role="tablist" + roving arrow-key keyboard, aria-selected/aria-orientation, each tab wired to its fold-tab-panel (aria-controls ↔ aria-labelledby). Panels take the bar by ref ([tabs]="t") so they coordinate across fold-nav-layout slots. |
| FoldIconComponent | fold-icon | SVG icon (~135 built-in glyphs across 7 categories incl. commerce + FoldIconRegistry). |
| FoldSpinnerComponent | fold-spinner | Indeterminate loading arc (currentColor, icon-sized, reduced-motion aware). Decorative by default; label → role="status". Powers loading on the buttons. |
| FoldAvatarComponent / …Detail | fold-avatar | Initials/image avatar (square, muted, status ring) + identity cell. |
| FoldAvatarListComponent | fold-avatar-list | Overlapping avatar cluster (per-face variant, limit + a +N overflow chip). |
| FoldToastComponent / …Container | fold-toast | Frosted snackbar (variant glyph + dismiss) + queue host (+ FoldToastService). |
| FoldLoadingStateComponent | fold-loading | Loading placeholder — fold-spinner + message, in a role="status" region; size input; stretches to fill. |
| FoldEmptyStateComponent | fold-empty-state | Empty-state block (icon + title + message + optional action). |
| FoldCalloutComponent | fold-callout | Tinted message row — status colour + icon + message + optional trailing actions; inset (bordered, in-flow) appearance. |
| FoldDisclosureComponent | fold-disclosure | One summary toggling one collapsible panel — the accordion primitive (open-state is the consumer's to bind); keeps content mounted, unlike native <details>. |
| FoldPanelHostComponent | fold-panel-host | Side-panel / overlay host (+ FoldPanelHostService / FoldPanelRef / FoldPanelToggle). Modal: accessible name, inert background barrier, top-most focus trap, scroll-lock. Localise the close label once via provideFoldPanelLabels({ close }). |
| FoldPanelHeaderComponent | fold-panel-header | Standard panel header (title/eyebrow, self-closing). Names its dialog (aria-labelledby) and reads the app-wide close label.
