Skip to content
Brok UI

Loading…

No results

Overlay

Open source

The shared anatomy of every modal surface: header, title, description, footer and close. Dialog, alert dialog, sheet and drawer compose these parts, so the four panels keep one spacing, type and close-affordance recipe.

Version
v1.0.1
Stability
stable
License
MIT
Related
Dialog
Alert Dialog
Sheet
Drawer

Preview

Delete project

This removes the project and every deployment attached to it. You cannot undo this.

previews.components.overlay.default.blade.php Blade
{{-- The overlay parts on a static panel, outside a modal, so the shared spacing
     and type recipe is visible. Dialog, alert dialog, sheet and drawer render
     the same markup with their own data-slot names. --}}
<x-ui.overlay class="w-full max-w-md">
    <x-ui.overlay.close />

    <x-ui.overlay.header>
        <x-ui.overlay.title :track="false">{{ __('Delete project') }}</x-ui.overlay.title>
        <x-ui.overlay.description :track="false">
            {{ __('This removes the project and every deployment attached to it. You cannot undo this.') }}
        </x-ui.overlay.description>
    </x-ui.overlay.header>

    <x-ui.overlay.footer>
        <x-ui.button variant="outline">{{ __('Cancel') }}</x-ui.button>
        <x-ui.button variant="destructive">{{ __('Delete project') }}</x-ui.button>
    </x-ui.overlay.footer>
</x-ui.overlay>

Installation

terminal
php artisan ui:add overlay

Registry contract

php artisan ui:add overlay writes only the files below. The CLI validates each file hash before writing and asks before it replaces a local change, unless you pass --force.

  • blade resources/views/components/ui/overlay.blade.php
  • blade resources/views/components/ui/overlay/header.blade.php
  • blade resources/views/components/ui/overlay/title.blade.php
  • blade resources/views/components/ui/overlay/description.blade.php
  • blade resources/views/components/ui/overlay/footer.blade.php
  • blade resources/views/components/ui/overlay/close.blade.php
Registry dependencies
None — installs on its own.
Packages
composer: jml/brok:^0.2

Use with AI

A brief for your coding agent: install command, usage, props, guidance and the rules. Copy it, or open a prompt about this component in an assistant.

overlay.md
# Brok UI: Overlay (`overlay`)

The shared anatomy of every modal surface: header, title, description, footer and close. Dialog, alert dialog, sheet and drawer compose these parts, so the four panels keep one spacing, type and close-affordance recipe.

Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.

## Install

```bash
php artisan ui:add overlay
```

## Usage

```blade
{{-- The overlay parts on a static panel, outside a modal, so the shared spacing
     and type recipe is visible. Dialog, alert dialog, sheet and drawer render
     the same markup with their own data-slot names. --}}
<x-ui.overlay class="w-full max-w-md">
    <x-ui.overlay.close />

    <x-ui.overlay.header>
        <x-ui.overlay.title :track="false">{{ __('Delete project') }}</x-ui.overlay.title>
        <x-ui.overlay.description :track="false">
            {{ __('This removes the project and every deployment attached to it. You cannot undo this.') }}
        </x-ui.overlay.description>
    </x-ui.overlay.header>

    <x-ui.overlay.footer>
        <x-ui.button variant="outline">{{ __('Cancel') }}</x-ui.button>
        <x-ui.button variant="destructive">{{ __('Delete project') }}</x-ui.button>
    </x-ui.overlay.footer>
</x-ui.overlay>
```

## Props

- `sticky` (bool, default `false`) — Declared by @props in the registry Blade source.
- `scope` (string, default `overlay-title`) — Declared by @props in the registry Blade source.
- `track` (bool, default `true`) — Declared by @props in the registry Blade source.

## Use when

- Use for short, self-contained work that benefits from preserving page context, or for interruptions that truly deserve focused attention.
- Building a modal surface of your own that should match dialog, alert dialog, sheet and drawer.
- Changing the header/footer spacing or the close affordance for every overlay at once.

## Avoid when

- Do not use for long forms, multi-step tasks, or broad comparison work that needs more room and less interruption.
- You need a modal surface itself — compose dialog, alert-dialog, sheet or drawer, which already wrap these parts.

## Anti-patterns

- Long forms in dialogs
- Multi-step or comparison-heavy modal workflows

## Rules

- Use the `<brok:overlay>` tag (or `<x-ui.overlay>`) in Blade; do not rewrite the component.
- Prefer the documented props and variants over utility-class overrides; when a utility must win, use the `!` important modifier.
- Use semantic design tokens (`bg-primary`, `text-muted-foreground`), never raw colour utilities.
- Keep the `data-slot` attributes; they are the styling and test hooks.

## Links

- Docs: https://brokui.dev/docs/components/overlay
- Registry JSON (files, props, contract): https://brokui.dev/r/open/overlay.json

Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.

API

manifest knowledge + registry-derived coverage

Props

Props accepted by this component: name, type, default value and description.
Prop Type Default Description
sticky bool false Declared by @props in the registry Blade source.
scope string overlay-title Declared by @props in the registry Blade source.
track bool true Declared by @props in the registry Blade source.

Slots

  • default — Primary Blade slot rendered by the component.
  • x-ui.overlay.header — Installed subcomponent from the registry item.
  • x-ui.overlay.title — Installed subcomponent from the registry item.
  • x-ui.overlay.description — Installed subcomponent from the registry item.
  • x-ui.overlay.footer — Installed subcomponent from the registry item.
  • x-ui.overlay.close — Installed subcomponent from the registry item.

Data slots

Stable hooks for CSS overrides and browser tests.

overlay {{ $slotName }}

Behavior

  • Each part prints exactly one data-slot: the composing overlay's override when given, its own overlay-<part> name otherwise.
  • The close button calls hide() on the surrounding overlay's Alpine scope.
  • Spacing reads the density-aware dialog/stack tokens, so a compact or comfortable density retunes every overlay together.
  • Declares registry capability flags: a11y, responsive, rtl, darkMode, localized.

Guidance

Focused overlay

Handle a short focused interruption or blocking decision.

Use when

  • Use for short, self-contained work that benefits from preserving page context, or for interruptions that truly deserve focused attention.
  • Building a modal surface of your own that should match dialog, alert dialog, sheet and drawer.
  • Changing the header/footer spacing or the close affordance for every overlay at once.

Avoid when

  • Do not use for long forms, multi-step tasks, or broad comparison work that needs more room and less interruption.
  • You need a modal surface itself — compose dialog, alert-dialog, sheet or drawer, which already wrap these parts.

Use instead

  • Inline content
  • Drawer or dedicated page for longer tasks

Anti-patterns

  • Long forms in dialogs
  • Multi-step or comparison-heavy modal workflows
Anatomy
root header title description footer close
Theming hooks
overlay header overlay footer overlay close

Accessibility

WCAG 2.2 AA Keyboard focus-visible RTL-ready Localized labels Dark mode
Keyboard
native
Focus
native
  • Always render a title inside the header; add a description when the purpose is not obvious.
  • The close button carries a visually hidden label and an aria-hidden icon.
  • Semantic HTML and a stable data-slot attribute for styling and scripting hooks.
  • Focus-visible rings use the ring token, so keyboard focus is always visible.
  • Disabled and invalid states are conveyed to assistive tech, not by color alone.
  • Targets WCAG 2.2 AA; verify contrast in light, dark, admin and customer surfaces in the preview.
  • Labels go through __() and layout uses logical properties (ms-*, text-start), so it mirrors under dir="rtl" — flip the preview to RTL to confirm.
  • Dark mode uses the same semantic tokens under the dark class; high contrast follows forced-color system tokens.

Livewire

Needs wire:key

Add a stable wire:key when Livewire can reorder this interactive component.

livewire-component.blade.php Blade
<div wire:key="overlay-{{ $record->id }}">
    {{-- The overlay parts on a static panel, outside a modal, so the shared spacing
         and type recipe is visible. Dialog, alert dialog, sheet and drawer render
         the same markup with their own data-slot names. --}}
    <x-ui.overlay class="w-full max-w-md">
        <x-ui.overlay.close />
    
        <x-ui.overlay.header>
            <x-ui.overlay.title :track="false">{{ __('Delete project') }}</x-ui.overlay.title>
            <x-ui.overlay.description :track="false">
                {{ __('This removes the project and every deployment attached to it. You cannot undo this.') }}
            </x-ui.overlay.description>
        </x-ui.overlay.header>
    
        <x-ui.overlay.footer>
            <x-ui.button variant="outline">{{ __('Cancel') }}</x-ui.button>
            <x-ui.button variant="destructive">{{ __('Delete project') }}</x-ui.button>
        </x-ui.overlay.footer>
    </x-ui.overlay>
</div>

Validation

Validation support: native. Keep the error message connected with aria-describedby.

livewire-form.blade.php Blade
<form wire:submit="save" class="space-y-2">
    <brok:overlay
        wire:model="value"
        :aria-invalid="$errors->has('value') ? 'true' : 'false'"
        aria-describedby="value-error"
    />

    @error('value')
        <p id="value-error" role="alert">{{ $message }}</p>
    @enderror

    <brok:button type="submit" wire:loading.attr="disabled">
        <span wire:loading.remove>Save</span>
        <span wire:loading>Saving…</span>
    </brok:button>
</form>

Source

The exact, editable files ui:add writes into your app. Previews render this same code; there are no preview-only components.

resources/views/components/ui/overlay.blade.php Blade
@php
    $styles = require base_path(config('ui.component_path', 'resources/views/components/ui').'/_styles.php');
@endphp

{{--
    Overlay — the panel surface the shared anatomy sits on. Dialog, alert
    dialog, sheet and drawer render their own panels (with open state, focus
    trap and teleport); use this root for a custom or static surface that
    should match them, then compose <x-ui.overlay.header>, .title,
    .description, .footer and .close inside it.
--}}
<div
    data-slot="overlay"
    {{ $attributes->merge(['class' => $styles['overlay']['panel']]) }}
>
    {{ $slot }}
</div>
resources/views/components/ui/overlay/header.blade.php Blade
@props([
    // Pin the header to the top of a scrollable overlay body.
    'sticky' => false,
])

@php
    $styles = require base_path(config('ui.component_path', 'resources/views/components/ui').'/_styles.php');
    // dialog/alert-dialog/sheet/drawer each pass their own name so the anatomy
    // selector stays `dialog-header`, `sheet-header`, … The browser keeps only
    // the first of two identical attributes, so read the override out of the
    // bag and print exactly one.
    $slotName = (string) ($attributes->get('data-slot') ?: 'overlay-header');
    $classes = $styles['overlay']['header'].($sticky ? ' '.$styles['overlay']['headerSticky'] : '');
@endphp

<div
    data-slot="{{ $slotName }}"
    {{ $attributes->except('data-slot')->merge(['class' => $classes]) }}
>
    {{ $slot }}
</div>
resources/views/components/ui/overlay/title.blade.php Blade
@props([
    // The Alpine `$id()` scope name the panel's aria-labelledby points at.
    'scope' => 'overlay-title',
    // Report the title to the overlay root (`hasTitle`) so the panel can label
    // itself only when a title is actually present. alert-dialog labels its
    // panel unconditionally and has no such flag, so it opts out.
    'track' => true,
])

@php
    $styles = require base_path(config('ui.component_path', 'resources/views/components/ui').'/_styles.php');
    $slotName = (string) ($attributes->get('data-slot') ?: 'overlay-title');
@endphp

<h2
    data-slot="{{ $slotName }}"
    x-bind:id="$id('{{ $scope }}')"
    @if ($track) x-init="hasTitle = true" @endif
    {{ $attributes->except('data-slot')->merge(['class' => $styles['overlay']['title']]) }}
>
    {{ $slot }}
</h2>
resources/views/components/ui/overlay/description.blade.php Blade
@props([
    // The Alpine `$id()` scope name the panel's aria-describedby points at.
    'scope' => 'overlay-description',
    // Report the description to the overlay root (`hasDescription`). See
    // title.blade.php — alert-dialog opts out.
    'track' => true,
])

@php
    $styles = require base_path(config('ui.component_path', 'resources/views/components/ui').'/_styles.php');
    $slotName = (string) ($attributes->get('data-slot') ?: 'overlay-description');
@endphp

<p
    data-slot="{{ $slotName }}"
    x-bind:id="$id('{{ $scope }}')"
    @if ($track) x-init="hasDescription = true" @endif
    {{ $attributes->except('data-slot')->merge(['class' => $styles['overlay']['description']]) }}
>
    {{ $slot }}
</p>
resources/views/components/ui/overlay/footer.blade.php Blade
@props([
    // Pin the footer to the bottom of a scrollable overlay body.
    'sticky' => false,
])

@php
    $styles = require base_path(config('ui.component_path', 'resources/views/components/ui').'/_styles.php');
    $slotName = (string) ($attributes->get('data-slot') ?: 'overlay-footer');
    $classes = $styles['overlay']['footer'].($sticky ? ' '.$styles['overlay']['footerSticky'] : '');
@endphp

<div
    data-slot="{{ $slotName }}"
    {{ $attributes->except('data-slot')->merge(['class' => $classes]) }}
>
    {{ $slot }}
</div>
resources/views/components/ui/overlay/close.blade.php Blade
@props([
    // In a scrollable overlay, float the close button so it stays pinned to the
    // panel's top-right while the body scrolls (sticky instead of absolute).
    'sticky' => false,
])

@php
    $styles = require base_path(config('ui.component_path', 'resources/views/components/ui').'/_styles.php');
    $slotName = (string) ($attributes->get('data-slot') ?: 'overlay-close');
    $classes = ($sticky ? $styles['overlay']['closeSticky'] : $styles['overlay']['closeAnchored']).' '.$styles['overlay']['close'];
@endphp

<button
    type="button"
    data-slot="{{ $slotName }}"
    @click="hide()"
    {{ $attributes->except('data-slot')->merge(['class' => $classes]) }}
>
    <span class="sr-only">{{ __('Close') }}</span>
    <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4">
        <path d="M18 6 6 18M6 6l12 12" />
    </svg>
</button>

Ownership & lifecycle

Owner, release state, review evidence and adoption for this item.
Owner
Platform UI (@JoshJML)
Current version
1.0.1
Status
Stable
License
open
Accessibility reviewed
No review date recorded
Last breaking change
No date recorded
Deprecation
Not deprecated
Contract
v6
Foundation
≥ 1.0.0