Using components
Orbix components are plain template markup with fd-* classes (BEM naming) and, for interactive ones, a
data-fd-* attribute that the bundled behaviors pick up automatically when the page has rendered
(injectPageScript()): there is nothing else to initialize. The UI, Forms and Tables pages of the
template show every component; open the page template under src/pages/ to copy its markup. Reusable blocks are
standalone components in src/components/ (ProductCard, MailRow, Password, Otp, Example...): their
parameters are signal inputs, and blocks that take markup (actions, badges) use content projection.
Blocks whose markup has one root element use it as their host element (an attribute selector), so the markup stays exactly as designed:
<!-- in the template; import Password from '@/components/forms/password' in the component -->
<div oxPassword [id]="'new-password'" [autocomplete]="'new-password'" [meter]="'true'"></div>
<li oxProductCard [name]="'Aurora lamp'" [photo]="'desk-lamp'" [cat]="'home'" [price]="'89'" [hue]="'3'" [icon]="'lc-lamp'" [stars]="'4'" [rating]="'4.6'">
<ng-container ngProjectAs="[ox-badge]"><span class="fd-badge fd-badge--success">{{ t('apps.shop.new') }}</span></ng-container>
</li>Replace the translation keys with your own (see Languages); t and fmt are the component
members protected readonly t = t; and protected readonly fmt = formatValue;.
Buttons
<button type="button" class="fd-btn fd-btn--primary"><span class="fd-btn__label">{{ t('common.actions.save') }}</span></button>
<button type="button" class="fd-btn fd-btn--secondary"><span class="fd-btn__label">{{ t('common.actions.cancel') }}</span></button>
<button type="button" class="fd-btn fd-btn--ghost fd-btn--sm"><span class="fd-btn__label">{{ t('common.actions.preview') }}</span></button>
<button type="button" class="fd-btn fd-btn--danger"><span class="fd-btn__label">{{ t('common.actions.delete') }}</span></button>
<button type="button" class="fd-btn fd-btn--ghost fd-btn--icon" [attr.aria-label]="t('shell.settings')">
<svg class="fd-icon" aria-hidden="true"><use href="#lc-settings"/></svg>
</button>Cards
<article class="fd-card" aria-labelledby="card-title">
<header class="fd-card__header">
<div class="fd-card__heading">
<h2 class="fd-card__title" id="card-title">{{ t('reports.revenue.title') }}</h2>
<p class="fd-card__subtitle">{{ t('reports.revenue.period') }}</p>
</div>
</header>
<div class="fd-card__body"><p class="fd-text-muted">{{ t('reports.revenue.text') }}</p></div>
<footer class="fd-card__footer">
<button type="button" class="fd-btn fd-btn--primary fd-btn--sm"><span class="fd-btn__label">{{ t('reports.revenue.open') }}</span></button>
</footer>
</article>Stat (KPI) tiles
fmt() formats the value for the active language (number, currency, percent...):
<div class="fd-kpis">
<section class="fd-stat" aria-labelledby="kpi-orders">
<div class="fd-stat__header">
<h2 class="fd-stat__label" id="kpi-orders">{{ t('nav.orders') }}</h2>
<span class="fd-stat__icon" aria-hidden="true"><svg class="fd-icon"><use href="#lc-shopping-bag"/></svg></span>
</div>
<p class="fd-stat__value">{{ fmt('currency', 48250, { digits: 0 }) }}</p>
</section>
</div>Alerts and badges
<div class="fd-alert fd-alert--success" role="status">
<svg class="fd-icon fd-alert__icon" aria-hidden="true"><use href="#fd-i-success"/></svg>
<div class="fd-alert__content">
<p class="fd-alert__title">{{ t('billing.paid_title') }}</p>
<p>{{ t('billing.paid_text') }}</p>
</div>
</div>
<span class="fd-badge fd-badge--success">{{ t('common.status.paid') }}</span>
<span class="fd-badge fd-badge--warning">{{ t('common.status.pending') }}</span>
<span class="fd-badge fd-badge--danger">{{ t('common.status.overdue') }}</span>Form fields
<div class="fd-field">
<label class="fd-field__label" for="email">{{ t('common.fields.email') }}</label>
<input class="fd-input" id="email" type="email" autocomplete="email" [value]="email()" (input)="email.set($any($event.target).value)">
</div>
<div class="fd-field">
<label class="fd-field__label" for="website">{{ t('common.fields.website') }}</label>
<div class="fd-input-wrap">
<svg class="fd-icon fd-input-wrap__icon" aria-hidden="true"><use href="#lc-globe"/></svg>
<input class="fd-input" id="website" type="url">
</div>
</div>The demo forms are plain HTML forms with initial values; in your application bind the fields with signals (as
above), Angular forms (ReactiveFormsModule, FormsModule or signal forms: add @angular/forms) or a form library
as you prefer – the markup and classes stay the same. The password field with a visibility toggle and the
one-time code input are components: <div oxPassword [id]="'new-password'" [meter]="'true'"></div> and
<div oxOtp></div> (src/components/forms/).
Tables
<div class="fd-table-wrap" tabindex="0" role="region" [attr.aria-label]="t('nav.invoices')">
<table class="fd-table">
<thead>
<tr><th scope="col">{{ t('common.fields.invoice') }}</th><th scope="col">{{ t('common.fields.customer') }}</th><th scope="col">{{ t('common.fields.status') }}</th></tr>
</thead>
<tbody>
@for (invoice of invoices(); track invoice.id) {
<tr>
<td>{{ invoice.number }}</td>
<td>{{ invoice.customer }}</td>
<td><span class="fd-badge fd-badge--success">{{ t('common.status.paid') }}</span></td>
</tr>
}
</tbody>
</table>
</div>The Tables → Data table page shows the data grid with sorting, search, column filters, selection, export and
pagination (src/scripts/tables.ts). In your own pages use the [oxDataGrid] directive (src/directives/data-grid.ts):
import { OxDataGrid } from '@/directives/data-grid';
import type { GridOptions } from '@foundry/ui';
// imports: [OxDataGrid]
protected readonly grid: GridOptions<Invoice> = { rows: invoices, rowId: (r) => r.id, columns: [/* key, label, sortable, render */] };<div class="fd-datatable" [oxDataGrid]="grid"><!-- toolbar, table and footer markup as on the Data table page --></div>Modals
<button type="button" class="fd-btn fd-btn--primary" data-fd-modal-open="modal-invite" aria-haspopup="dialog">
<span class="fd-btn__label">{{ t('pages.team.invite') }}</span>
</button>
<dialog class="fd-modal fd-modal--sm" id="modal-invite" aria-labelledby="modal-invite-title" data-fd-modal>
<div class="fd-modal__header">
<div class="fd-modal__heading"><h2 class="fd-modal__title" id="modal-invite-title">{{ t('pages.team.invite') }}</h2></div>
</div>
<div class="fd-modal__body"><p>{{ t('pages.team.invite_text') }}</p></div>
<div class="fd-modal__footer">
<button type="button" class="fd-btn fd-btn--secondary" data-fd-modal-close><span class="fd-btn__label">{{ t('common.close') }}</span></button>
</div>
</dialog>From code: openModal(dialog) and closeModal(dialog) from @foundry/ui (get the element with viewChild() or
ElementRef); confirmations with await confirmDialog({ title, body, confirmLabel, cancelLabel, tone: 'danger' }).
Tabs
<div class="fd-tabs" data-fd-tabs>
<div class="fd-tabs__list" role="tablist" [attr.aria-label]="t('nav.settings')">
<button type="button" class="fd-tabs__tab" role="tab" id="tab-general" aria-controls="panel-general" aria-selected="true">{{ t('settings.general') }}</button>
<button type="button" class="fd-tabs__tab" role="tab" id="tab-billing" aria-controls="panel-billing" aria-selected="false" tabindex="-1">{{ t('nav.billing') }}</button>
</div>
<div class="fd-tabs__panel" role="tabpanel" id="panel-general" aria-labelledby="tab-general" tabindex="0">…</div>
<div class="fd-tabs__panel" role="tabpanel" id="panel-billing" aria-labelledby="tab-billing" tabindex="0" hidden>…</div>
</div>Dropdown menus
<div class="fd-dropdown" data-fd-dropdown>
<button type="button" class="fd-btn fd-btn--secondary" aria-haspopup="menu" aria-expanded="false" aria-controls="menu-actions">
<span class="fd-btn__label">{{ t('common.actions.actions') }}</span>
</button>
<div class="fd-menu" role="menu" id="menu-actions" hidden>
<button type="button" class="fd-menu__item" role="menuitem" tabindex="-1" (click)="edit()"><span class="fd-menu__label-text">{{ t('common.actions.edit') }}</span></button>
<hr class="fd-menu__separator">
<button type="button" class="fd-menu__item fd-menu__item--danger" role="menuitem" tabindex="-1" (click)="remove()"><span class="fd-menu__label-text">{{ t('common.actions.delete') }}</span></button>
</div>
</div>The menu also dispatches an fd:select event with the chosen item; regular (click) handlers on the items are the
simplest option in Angular.
Toasts
import { toast } from '@foundry/ui';
import { t } from '@/core/i18n';
export class AccountSettingsPage {
protected readonly t = t;
protected save(): void {
toast({ title: t('account.saved'), variant: 'success', closeLabel: t('common.dismiss') });
}
}<button type="button" class="fd-btn fd-btn--secondary" (click)="save()">
<span class="fd-btn__label">{{ t('common.actions.save') }}</span>
</button>Grid
Responsive 12-column grid with logical gaps:
<div class="fd-row">
<div class="fd-col-12 fd-col-md-6 fd-col-xl-4">…</div>
<div class="fd-col-12 fd-col-md-6 fd-col-xl-8">…</div>
</div>Markup attributes
A few blocks take attributes as markup text from their caller, for example a pre-checked checkbox or an open
accordion item ([open]="'open'" on oxFaqItem). The [oxAttrs] directive (src/directives/attrs.ts) sets them
as real HTML attributes, so checked, selected and open render on and stay the field's default state (a form
reset returns to them):
<input class="fd-check__input" type="checkbox" [oxAttrs]="'checked'">Accessibility
Components ship with the right roles and keyboard support (menus, tabs, modals, comboboxes, date pickers).
Keep the aria-* attributes from the examples, give every icon-only button an aria-label, and use
aria-hidden="true" on decorative icons.