Tibi

UI kit

The ui marketplace package (tibi plugin add ui) ships accessible components built on the platform: <dialog> for modals, the Popover API for floating panels (top layer, light dismiss, Esc), and native inputs rendered in the light DOM so they submit with forms, autofill and bind with t-bind. Everything is a <Ti…> tag; lists and callbacks are passed as value props (.items="list").

Try it

Solid Outline Soft Hover me PopoverPopoverClick outside or press Esc to close. Open modal

Esc or the backdrop closes it.

Components

  • <TiButton>: variant (solid, outline, soft, ghost, link), color (primary, neutral, success, warning, error), size (xs, sm, md, lg), href, type, block, loading, disabled. type="submit" submits the enclosing form.
  • <TiModal>: heading (use it rather than title, which also shows as a browser tooltip over the whole dialog), description, open, fullscreen, dismissible, backdrop="static" (backdrop clicks don't close it); slots trigger, default, footer; host.show() / close() / toggle(); events open, close; width from --ti-modal-width (32rem by default).
  • <TiPopover>: side, align, mode (click, hover), open; slots trigger and default.
  • <TiTooltip>: text, side, delay (ms), disabled.
  • <TiDropdownMenu .items>: items { label, href?, color?, disabled?, kbd?, style?, onSelect?, items? } (style styles the label), or groups (an array of arrays); event select. An item with items opens a submenu beside it (hover, click or ArrowRight; ArrowLeft or Esc goes back). Arrow keys, Home / End, Enter, Esc.
  • <TiSelectMenu .items>: strings or { label, value, disabled? }; value, multiple, searchable, placeholder, name (hidden inputs for form submission), disabled; event change; host.value.
  • <TiForm .validate>: runs native constraint validation and your validate(values) (sync or async, returning { field: message }), puts messages on the matching <TiFormField name>, then emits submit with { values, formData, form } or invalid. disabled disables every control.
  • <TiFormField>: label, name, description, hint, error, required, size. Wires the label to its control and clears the error as the user types.
  • <TiInput>: every native input attribute that matters (type, name, value, placeholder, required, min / max, pattern, …) plus size and variant (outline, soft, ghost).
  • <TiCheckbox>, <TiPinInput length otp> (emits complete), <TiColourPicker .swatches>, <TiDateTimePicker mode> (host.valueAsDate, host.iso; zoned ISO values convert to the visitor's local time).
  • <TiNavigationMenu .items orientation>: marks the current page with aria-current (longest matching path; exact per item) and follows client-side navigation.
  • <TiAlert>: color, variant (soft, solid, outline), title, description, dismissible; slots icon, default, actions. <TiSeparator orientation label>.
  • Toasts live in the separate toast package (<TiToastHost/> plus window.tibi.toast.show()).

A form

<TiForm .validate="check" @submit="save($event.detail.values)"> <TiFormField label="Handle" name="handle" required> <TiInput name="handle" t-bind="handle"/> </TiFormField> <TiFormField label="Tags" name="tags"> <TiSelectMenu name="tags" multiple searchable .items="tags"/> </TiFormField> <TiButton type="submit">Save</TiButton> </TiForm> <script tibi> const handle = state(''); const tags = ['music', 'video', 'shop']; const check = (v) => (v.handle.length < 3 ? { handle: 'Use at least 3 characters' } : {}); const save = (values) => fetch('/api/profile', { method: 'POST', body: JSON.stringify(values) }); </script>

Theming

Components read CSS custom properties with sensible fallbacks: --ti-primary (falls back to --brand), --ti-neutral, --ti-success, --ti-warning, --ti-error, --ti-info, --ti-surface, --ti-on-surface (text on the surface: menus, popovers, modals and fields; inherits when unset), --ti-border, --ti-border-strong, --ti-muted, --ti-radius, --ti-radius-lg, --ti-tooltip-bg, --ti-tooltip-fg. Set them on :root in your global CSS.

examples/ui-kit in the Tibi repo uses every component on one page.