# TallStackUI: Modal
> TallStackUI is a TALL Stack (Tailwind CSS, Alpine.js, Laravel, Livewire)
> component library providing 80+ Blade components for building modern web interfaces.
A modal overlay component for displaying content in a centered or top-aligned dialog with optional title, footer, blur backdrop, scrollable body, and configurable sizes. Can be controlled via `wire:model`, JavaScript API, or Livewire events.
The vertical alignment is responsive: `center` accepts a boolean or a Tailwind breakpoint, so the modal can be a bottom sheet on the phone and a centered dialog on the desktop.
## Basic Usage
```blade
Modal body content goes here.
```
```blade
Are you sure you want to continue?
```
Using `wire:model` for Livewire-controlled state:
```blade
Controlled by a Livewire property.
```
Centering only from a breakpoint upwards. Below it the modal stays a bottom sheet, at
every width, with no top-aligned band in between:
```blade
```
| Value | Alignment classes |
|---------|--------------------------------------------|
| `false` | `items-end sm:items-start` |
| `true` | `items-center` plus `p-4` and `rounded-xl` |
| `"sm"` | `items-end sm:items-center` |
| `"md"` | `items-end md:items-center` |
| `"lg"` | `items-end lg:items-center` |
| `"xl"` | `items-end xl:items-center` |
| `"2xl"` | `items-end 2xl:items-center` |
Only `center` as a boolean forces `p-4` and `rounded-xl`, which makes the modal float
free of the screen edges on mobile too. A breakpoint keeps the mobile bottom sheet flush
and relies on the `sm:` variants the wrapper blocks already carry, so from `sm` up the
sheet gains its padding and full radius while staying anchored to the bottom until the
chosen breakpoint takes over.
## Open Animation
Below `sm` the modal is a bottom sheet and slides in opaque from off screen, with no
fade, over 400ms of `ease-emphasized-decelerate`. It leaves over 200ms of
`ease-emphasized-accelerate`. From `sm` up it is a floating dialog again and fades while
it scales. The backdrop fades in both cases.
`center` as a boolean never slides: it is a centered dialog at every width, so it scales
in even on a phone. A breakpoint keeps the sheet, and therefore the slide, below `sm`.
Under `prefers-reduced-motion` the sheet stops travelling and fades instead.
The two easing tokens live in `css/v4.css` and are available to any component:
```css
--ease-emphasized-decelerate: cubic-bezier(0.05, 0.7, 0.1, 1);
--ease-emphasized-accelerate: cubic-bezier(0.3, 0, 0.8, 0.15);
```
Removing the body padding so the content bleeds to the edges. The title and the
footer keep theirs:
```blade
```
Distributing the footer actions instead of pushing them all to the end:
```blade
Body content.
```
Taking over the footer layout with `unwrapped`. The border and the padding of
the footer area stay; only the aligning wrapper is dropped:
```blade
Body content.
```
## Attributes
| Attribute | Type | Default | Description |
|-------------|--------------------|----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| id | string\|null | 'modal' | Unique identifier used for targeting with JS API and events |
| zIndex | string\|null | null (from config: 'z-50') | CSS z-index class |
| wire | string\|bool\|null | null | Livewire entangle property name (string) or boolean to use default 'modal' |
| title | string\|null | null | Title text displayed in the modal header with a close button |
| footer | string\|null | null | Footer slot content |
| blur | bool\|string\|null | null (from config: false) | Backdrop blur effect (false, sm, md, lg, xl, or true for sm) |
| persistent | bool\|null | null (from config: false) | When true, prevents closing via outside click or Escape key |
| size | string\|null | null (from config: '2xl') | Modal width (sm, md, lg, xl, 2xl, 3xl, 4xl, 5xl, 6xl, 7xl, full) |
| entangle | string\|null | 'modal' | Livewire property name for entangle binding |
| center | bool\|string\|null | null (from config: false) | `true` centers on all viewport sizes with full border radius and padding (v2-style). A breakpoint (sm, md, lg, xl, 2xl) centers only from that width upwards, staying a bottom sheet below it |
| overflow | bool\|null | null (from config: false) | When true, avoids hiding body overflow |
| scrollable | bool\|null | null (from config: false) | When true, fixes title and footer while body scrolls |
| paddingless | bool\|null | null | When true, removes the padding of the body, leaving the default slot flush against the modal edges. Title and footer keep their padding |
| handle | bool\|null | null (from config: false) | Displays a grabber bar on mobile only (`sm:hidden`) allowing the bottom sheet to be dragged down to close. Releases beyond 25% of the panel height dismiss; pulling upwards meets rubber band resistance |
## Slots
| Slot | Description |
|-----------|----------------------------------------------------------------|
| (default) | Main body content of the modal |
| title | Title text or custom markup in the modal header (as attribute) |
| footer | Content rendered in the modal footer area |
### Footer Slot Attributes
| Attribute | Description |
|-----------|--------------------------------------------------------------------|
| start | Aligns the footer content to the start |
| center | Centers the footer content |
| end | Aligns the footer content to the end (default when none is passed) |
| between | Distributes the footer content with space between |
| unwrapped | Drops the aligning wrapper, keeping the footer border and padding |
## Validation Constraints
- The `wire` property cannot be an empty string.
- The `size` must be one of: sm, md, lg, xl, 2xl, 3xl, 4xl, 5xl, 6xl, 7xl, full.
- The `center` must be a boolean or one of: sm, md, lg, xl, 2xl. This rejects `center="desktop"` and `center="true"`, since Blade hands a quoted attribute over as a string. Arbitrary values such as `center="min-[900px]"` are not supported: Tailwind only generates classes it can read literally in the source.
- The `handle` cannot be used when `center` is fully enabled (`center` as `true`): a centered modal never behaves as a bottom sheet. Breakpoint values are fine.
- The `zIndex` must start with `z-` prefix.
- The `footer` slot cannot combine two or more alignments.
- The `footer` slot cannot use `unwrapped` together with an alignment.
## JavaScript Control
```js
$tsui.open.modal('my-modal')
$tsui.close.modal('my-modal')
```
## Configuration
In `config/tallstackui.php` under `components.modal`:
| Option | Type | Default | Description |
|------------|---------------|---------|---------------------------------------------------------------------------------------------------------------|
| z-index | string | 'z-50' | Default z-index class |
| overflow | bool | false | When true, avoids hiding body overflow |
| blur | false\|string | false | Backdrop blur effect (false, sm, md, lg, xl) |
| persistent | bool | false | When true, prevents closing by clicking outside |
| size | string | '2xl' | Default modal width |
| center | bool\|string | false | When true, centers on every viewport. A breakpoint (sm, md, lg, xl, 2xl) centers only from that width upwards |
| scrollable | bool | false | When true, fixes title/footer while body scrolls |
| scrollbar | string\|null | 'thin' | Scrollbar style for scrollable mode (null, thin, thick) |
| handle | bool | false | Displays the mobile drag-to-close grabber by default (the inline prop always wins) |
## Wireable Mode (Livewire Property Binding)
Use `wire` to bind modal visibility to a Livewire boolean property:
```blade
TallStackUi
Open
```
Custom property name:
```blade
TallStackUi
Open
```
The property may already be `true` on the first render, set in `mount()` or through
`#[Url]` for instance. The modal then opens with the page and behaves like any other open
modal: Escape and a click outside close it, the page scroll is locked and `x-on:open` is
dispatched.
## Alpine.js Events
```blade
TallStackUi
```
## Focus Helper
Auto-focus an input when the modal opens using `$tsui.focus()`:
```blade
```
Optional delay in milliseconds: `$tsui.focus('email', 1000)`
You can also target elements by `data-focus` attribute instead of `id`:
```blade
```
## Soft Customization
Soft customization allows you to override default Tailwind CSS classes used by this component at runtime, either through a service provider or scoped per-instance.
### Customization
```php
TallStackUi::customize()
->modal()
->block('wrapper.first', 'your-tailwind-classes');
```
### Available Blocks
| Block Name | Purpose |
|----------------------|-------------------------------------------------|
| wrapper.first | Fixed overlay backdrop |
| wrapper.second | Fixed full-screen scroll container |
| wrapper.third | Centering flex container with size constraint |
| wrapper.fourth | Modal card with rounded corners and shadow |
| wrapper.scrollable | Max-height constraint for scrollable mode |
| positions.top | Alignment classes for top-positioned modal |
| positions.center | Alignment classes for centered modal |
| positions.center-sm | Alignment when centering from `sm` upwards |
| positions.center-md | Alignment when centering from `md` upwards |
| positions.center-lg | Alignment when centering from `lg` upwards |
| positions.center-xl | Alignment when centering from `xl` upwards |
| positions.center-2xl | Alignment when centering from `2xl` upwards |
| blur.sm | Small backdrop blur effect |
| blur.md | Medium backdrop blur effect |
| blur.lg | Large backdrop blur effect |
| blur.xl | Extra-large backdrop blur effect |
| title.wrapper | Title bar container with border |
| title.text | Title heading styles |
| title.close | Close button icon styles |
| handle.wrapper | Grabber strip container (mobile only) |
| handle.bar | Grabber bar styles |
| body | Body content area styles |
| body.scrollable | Scrollable body overflow styles |
| body.paddingless | Padding reset applied when `paddingless` is set |
| footer.wrapper | Footer container with border and padding |
| footer.scrollable | Sticky footer styles for scrollable mode |
| footer.base | Footer aligning wrapper (flex row) |
| footer.start | Footer alignment applied by `start` |
| footer.center | Footer alignment applied by `center` |
| footer.end | Footer alignment applied by `end` (default) |
| footer.between | Footer alignment applied by `between` |