---
title: "Livewire Kit Components (Flux UI)"
description: "The x-common Blade wrappers around Flux, the confirm dialog, modals, icons, toasts, shared Blade components and the app shell in the Livewire kit."
---
# Components
## Overview
The components are Blade components in `resources/views/components`, split like this:
| Folder | What it holds |
| --- | --- |
| `components/common/` | The `x-common.*` wrappers: every form control, button, modal, menu, badge and icon |
| `components/` | Kit-wide Blade components (`x-page-heading`, `x-table-card`, `x-stat-card`, `x-breadcrumbs`, …) and app shell pieces (`x-user-menu`, `x-app-header-actions`, `x-app-logo`) |
| `pages//partials/` | Components that belong to one area, used as `` |
| `views/livewire/` | Views of the shared Livewire widgets (`notification-bell`, `language-switcher`, …) |
Pages and feature components never use Flux tags directly. Every control goes through `x-common.*`, so the rest of the code doesn't know which UI library is underneath.
## Common components
| Component | Wraps | Notes |
| --- | --- | --- |
| `x-common.input` | `flux:input` | Adds the label and the error. Props: `label`, `required`, `optional`, `hint` |
| `x-common.password` | `x-common.input` | `type="password"` with Flux's `viewable` toggle |
| `x-common.textarea` | `flux:textarea` | Same label and error handling as the input |
| `x-common.select` | `flux:select` | Takes `options` (`[['value' => …, 'label' => …]]`) and `placeholder`, or your own options in the slot |
| `x-common.select-option`, `x-common.select-options` | `flux:select.option` | One option, or a loop over an `options` array |
| `x-common.checkbox` | Native checkbox | Styled `` with a check icon, label and error |
| `x-common.radio-group`, `x-common.radio` | `flux:radio.group`, `flux:radio` | |
| `x-common.input-group` | Native input | Input with a `prefix` and/or `suffix` addon, e.g. `https://` … `.example.com` |
| `x-common.label` | Native label | `required` (asterisk), `optional` (translated tag), `hint` |
| `x-common.error` | `flux:error` | Shows the validation message for `name` |
| `x-common.button` | `flux:button` | Flux props pass through: `variant`, `size`, `square`, `icon`, `href`, `loading` |
| `x-common.button-row` | Own markup | Floating Save / Discard bar that appears when the form is dirty |
| `x-common.link` | `flux:link` | |
| `x-common.badge`, `x-common.alert` | `flux:badge`, `flux:callout` | |
| `x-common.heading`, `x-common.text`, `x-common.separator` | `flux:heading`, `flux:text`, `flux:separator` | |
| `x-common.tooltip` | `flux:tooltip` | `content` as a string, or as a named slot for rich content |
| `x-common.modal`, `x-common.modal-trigger`, `x-common.modal-close` | `flux:modal`, `flux:modal.trigger`, `flux:modal.close` | See [Modals](/docs/livewire/livewire#modals) |
| `x-common.confirm-dialog` | `x-common.modal` | Delete confirmation calling a component action |
| `x-common.dropdown`, `x-common.menu`, `x-common.menu-item`, `x-common.menu-separator`, `x-common.menu-radio-group`, `x-common.menu-radio` | `flux:dropdown`, `flux:menu.*` | Used by the user menu, language switcher and notification bell |
| `x-common.otp`, `x-common.otp-group`, `x-common.otp-input` | `flux:otp.*` | Two-factor code input |
| `x-common.form-section` | Own markup | Card with an icon, title, description and step tag (Create tenant) |
| `x-common.icon` | `` | See [Icons](#icons) |
The field components (`input`, `textarea`, `select`, `checkbox`) work out three things from `wire:model` (or `name` when there's no `wire:model`):
- the `id`, with dots replaced by underscores, so `wire:model="form.company"` gets `id="form_company"`
- the label's `for`, pointing at that `id`
- the `x-common.error` underneath, reading the error for the same key
So a field with its label and error fits on one line:
```blade
```
Any other attribute (`autocomplete`, `placeholder`, `class`, `wire:model.live`, `x-model`, `data-test`, …) is passed through to the Flux component.
### Swapping the UI library
Most wrappers are a single line. `button.blade.php`, for example, is just this:
```blade
{{ $slot }}
```
If you want to move to Flux Pro or another library, edit the files in `components/common` and leave the pages alone. Flux is used directly only in the app shell: `layouts/base.blade.php` (toasts), `layouts/app/sidebar.blade.php`, `layouts/app/header.blade.php`, `layouts/app/partials/sidebar-menu.blade.php` and `components/app-logo.blade.php`.
### Button row
`x-common.button-row` uses `wire:dirty`, so it only slides in once a `wire:model` value has changed. With no `save` prop the Save button submits the surrounding form; `discard` names the component method to call.
```blade
```
### Confirm dialog
`x-common.confirm-dialog` is a modal with a red icon, an optional details list, a warning and a confirm button that calls `action` on the component. The component opens it with `Flux::modal($name)->show()`.
```blade
```
Props: `name`, `title`, `action` (required), `message`, `warning`, `details` (label ⇒ value), `confirmText`, `cancelText` and `keyword`. With a `keyword`, the user has to type it before the confirm button enables.
The full flow (pick the record, open the dialog, delete) is in [Livewire → Confirm and delete](/docs/livewire/livewire#confirm-and-delete).
## Icons & toasts
| Need | Use |
| --- | --- |
| Icon in a view | `` |
| Icon stored in the database (menus) | The same component with the stored name: `` |
| Icon on a Flux button or menu item | Flux's own `icon` prop (Heroicons), e.g. `icon="cog-6-tooth"` on `x-common.menu-item` |
| Toast from a component | `Flux::toast(variant: 'success', text: …)`, see [Livewire → Toasts](/docs/livewire/livewire#toasts) |
### Icons
`x-common.icon` renders the `iconify-icon` web component, which `resources/js/app.js` imports. Pass any Iconify name with a prefix (`lucide:building-2`, `flowbite:users-group-solid`, `logos:laravel`). A name without a prefix is turned into a Lucide name, so `LayoutGrid` becomes `lucide:layout-grid`.
```blade
```
Menu icons are stored the same way in the `menus` table (`'icon' => 'lucide:layout-grid'` in `MenuSeeder`), so the sidebar renders them with the same component.
## Shared Blade components
| Component | Props | Purpose |
| --- | --- | --- |
| `x-page-heading` | `title`, `description`, `largeActions` | Page title with actions in the slot |
| `x-table-card` | `paginator`, `searchPlaceholder`, `searchLabel`, `showing`, `pageLabel`; `filters` slot | Card with a search box, the table in the slot and pagination |
| `x-search-input` | `placeholder`, `label`, `model` (`'search'`), `compact` | Search box bound with `wire:model.live.debounce.300ms` |
| `x-pagination` | `paginator`, `showing`, `compact` | Page links calling `gotoPage()` |
| `x-filter-tabs` | `tabs`, `active`, `action` | Segmented filter that calls `action('')` |
| `x-stat-card` | `label`, `value`, `icon`, `tone` | Stat tile (`neutral`, `primary`, `success`, `warning`, `danger`, `info`) |
| `x-empty-state` | `icon`, `title`, `description` | Empty table row or list |
| `x-breadcrumbs` | `items` | Breadcrumb trail; links use `wire:navigate` |
| `x-dialog-header`, `x-dialog-footer` | `title`, `description`, `icon` | Header and footer of a modal |
| `x-initials` | `name`, `fallback` | Two-letter initials |
| `x-error-status` | `status` | Body of the 403/404/500/503 pages |
| `x-auth-header`, `x-auth-session-status` | `title`, `description` / `status` | Auth page title and the session status line |
| `x-accept-invitation-form` | `email`, `company`, `passwordRules`, `submitLabel` | Shared by the tenant and user invitation pages |
| `x-passkey-registration`, `x-passkey-verify` | | Passkey enrolment and passkey sign-in (load `resources/js/passkeys.js` with `@assets`) |
| `x-placeholder-pattern` | | Striped placeholder on the dashboard |
A typical list page puts them together like this:
```blade
@canany(['Create User', 'Create Tenant User'])
{{ __('modules/user.index.add_user') }}
@endcanany
…
```
## Area partials
Blade components used by one area live in `pages//partials` and are called through the `pages::` namespace:
| Component | Used by |
| --- | --- |
| `x-pages::tenants.partials.status-badge` | Tenant list, tenant detail, domains |
| `x-pages::tenants.partials.domain-type-badge`, `domain-actions` | Domains page and the tenant's domain card |
| `x-pages::tenants.partials.detail-card`, `show-field`, `show-pill`, `show-stat` | Tenant detail page |
| `x-pages::tenants.partials.selects` | Industry, team size, status and work week selects (create and edit) |
| `x-pages::tenants.partials.delete-tenant-modal`, `domain-modals` | Confirm dialogs, plus the domain Livewire modals |
| `x-pages::settings.partials.option-cards`, `layout-preview` | Layout pickers on Settings → Layout and Setup → Layout |
| `x-pages::notifications.partials.item`, `icon`, `empty` | Notifications page and the notification bell |
## App shell components
| Component | Purpose |
| --- | --- |
| `layouts/app/partials/sidebar-menu.blade.php` | Sidebar menu tree (`flux:sidebar.*`), or the setup menu under `/setup` |
| `layouts/app/partials/navbar.blade.php` | Top navigation for the header layout, with Alpine dropdowns |
| `x-app-header-actions` | Language switcher, notification bell and user menu |
| `` | Language dropdown; "Default" follows the domain |
| `` | Unread count, latest notifications, mark as read. See [Notifications](/docs/core/notifications) |
| `x-user-menu` | User dropdown: settings link and logout |
| `x-app-logo`, `x-app-logo-icon` | Branding (`sidebar` prop for the sidebar brand) |
::: warning
Inside a component tag, use {{ Js::from($value) }} instead of `@js($value)`. Blade doesn't compile directives inside component attributes, so `@js()` would reach the browser as plain text.
:::
```blade
```