Skip to content
Brok UI

Loading…

No results

Skeleton

Open source Core

Loading placeholder with shape presets and composable layouts.

Version
v1.1.3
Stability
stable
License
MIT
Related
Empty
Spinner
Text Loader

Preview

previews.components.skeleton.default.blade.php Blade
<div class="flex w-full max-w-sm items-center gap-4">
    <x-ui.skeleton class="size-12 rounded-full" />
    <div class="flex-1 space-y-2">
        <x-ui.skeleton class="h-4 w-3/4" />
        <x-ui.skeleton class="h-4 w-1/2" />
    </div>
</div>

Installation

terminal
php artisan ui:add skeleton

Registry contract

php artisan ui:add skeleton 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/skeleton.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.

skeleton.md
# Brok UI: Skeleton (`skeleton`)

Loading placeholder with shape presets and composable layouts.

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

## Install

```bash
php artisan ui:add skeleton
```

## Usage

```blade
<div class="flex w-full max-w-sm items-center gap-4">
    <x-ui.skeleton class="size-12 rounded-full" />
    <div class="flex-1 space-y-2">
        <x-ui.skeleton class="h-4 w-3/4" />
        <x-ui.skeleton class="h-4 w-1/2" />
    </div>
</div>
```

## Props

- `shape` (mixed|null, default `null`) — Preset shape: text, circle, rect, rounded, or null for a bare rounded bar.
- `animated` (bool, default `true`) — Pulses the placeholder; false renders a static block.

## Use when

- Use while content or system work is in progress so users understand the interface is responding.
- Placeholding content that is still loading, so the layout does not jump once real content arrives.
- Building a text, card, list, table, media, or stat placeholder from a preset shape, or composing a custom layout from bare bars.

## Avoid when

- Do not use indefinite loading indicators when you can show structure, progress, or a next step instead.
- Prefer over a spinner when the layout is known and can be previewed without implying false progress.
- Replacing content that has finished loading with no data; use empty instead.
- Needing a labelled, announced busy indicator; use spinner or a loader component, since skeleton is aria-hidden.

## Anti-patterns

- Indefinite loading with no progress or recovery

## Rules

- Use the `<brok:skeleton>` tag (or `<x-ui.skeleton>`) 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/skeleton
- Registry JSON (files, props, contract): https://brokui.dev/r/open/skeleton.json

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

Examples

card.blade.php Blade
<div class="w-full max-w-sm overflow-hidden rounded-xl border border-border bg-card p-4" role="status" aria-busy="true" aria-label="{{ __('Loading card') }}">
    <x-ui.skeleton shape="rounded" class="h-40" />
    <div class="mt-4 space-y-2">
        <x-ui.skeleton class="h-5 w-1/2" />
        <x-ui.skeleton shape="text" />
        <x-ui.skeleton shape="text" class="w-3/4" />
    </div>
    <div class="mt-4 flex items-center gap-3">
        <x-ui.skeleton shape="circle" class="size-9" />
        <div class="flex-1 space-y-1.5">
            <x-ui.skeleton class="h-3 w-1/3" />
            <x-ui.skeleton class="h-3 w-1/4" />
        </div>
    </div>
    <span class="sr-only">{{ __('Loading…') }}</span>
</div>
list.blade.php Blade
<div class="w-full max-w-sm space-y-4" role="status" aria-busy="true" aria-label="{{ __('Loading list') }}">
    @foreach (range(1, 4) as $row)
        <div class="flex items-center gap-4">
            <x-ui.skeleton shape="circle" class="size-10" />
            <div class="flex-1 space-y-2">
                <x-ui.skeleton class="h-4 w-3/4" />
                <x-ui.skeleton class="h-3 w-1/2" />
            </div>
            <x-ui.skeleton class="h-8 w-16 rounded-md" />
        </div>
    @endforeach
    <span class="sr-only">{{ __('Loading…') }}</span>
</div>
media.blade.php Blade
<div class="flex w-full max-w-md gap-4" role="status" aria-busy="true" aria-label="{{ __('Loading media') }}">
    <x-ui.skeleton shape="rounded" class="h-24 w-24 shrink-0" />
    <div class="flex-1 space-y-2">
        <x-ui.skeleton class="h-5 w-2/3" />
        <x-ui.skeleton shape="text" />
        <x-ui.skeleton shape="text" class="w-5/6" />
        <div class="flex gap-2 pt-1">
            <x-ui.skeleton class="h-7 w-20 rounded-full" />
            <x-ui.skeleton class="h-7 w-20 rounded-full" />
        </div>
    </div>
    <span class="sr-only">{{ __('Loading…') }}</span>
</div>
shapes.blade.php Blade
<div class="flex w-full max-w-md flex-wrap items-end gap-6">
    <div class="space-y-2">
        <span class="text-xs text-muted-foreground">{{ __('Circle') }}</span>
        <x-ui.skeleton shape="circle" />
    </div>

    <div class="flex-1 space-y-2">
        <span class="text-xs text-muted-foreground">{{ __('Text') }}</span>
        <x-ui.skeleton shape="text" />
    </div>

    <div class="w-full space-y-2">
        <span class="text-xs text-muted-foreground">{{ __('Rectangle') }}</span>
        <x-ui.skeleton shape="rect" />
    </div>

    <div class="w-full space-y-2">
        <span class="text-xs text-muted-foreground">{{ __('Rounded') }}</span>
        <x-ui.skeleton shape="rounded" />
    </div>
</div>
stat.blade.php Blade
<div class="grid w-full max-w-lg grid-cols-1 gap-4 sm:grid-cols-3" role="status" aria-busy="true" aria-label="{{ __('Loading stats') }}">
    @foreach (range(1, 3) as $card)
        <div class="rounded-xl border border-border bg-card p-4">
            <div class="flex items-center justify-between">
                <x-ui.skeleton class="h-3 w-1/2" />
                <x-ui.skeleton shape="circle" class="size-8" />
            </div>
            <x-ui.skeleton class="mt-4 h-8 w-2/3" />
            <x-ui.skeleton class="mt-2 h-3 w-1/3" />
        </div>
    @endforeach
    <span class="sr-only">{{ __('Loading…') }}</span>
</div>
static.blade.php Blade
<div class="flex w-full max-w-sm items-center gap-4">
    <x-ui.skeleton shape="circle" :animated="false" />
    <div class="flex-1 space-y-2">
        <x-ui.skeleton shape="text" :animated="false" class="w-3/4" />
        <x-ui.skeleton shape="text" :animated="false" class="w-1/2" />
    </div>
</div>
table.blade.php Blade
<div class="w-full max-w-lg overflow-hidden rounded-lg border border-border" role="status" aria-busy="true" aria-label="{{ __('Loading table') }}">
    <div class="flex items-center gap-4 border-b border-border bg-muted/40 px-4 py-3">
        <x-ui.skeleton class="h-4 w-1/4" />
        <x-ui.skeleton class="h-4 w-1/4" />
        <x-ui.skeleton class="h-4 w-1/5" />
        <x-ui.skeleton class="ms-auto h-4 w-16" />
    </div>
    @foreach (range(1, 5) as $row)
        <div class="flex items-center gap-4 border-b border-border px-4 py-3 last:border-b-0">
            <x-ui.skeleton class="h-4 w-1/4" />
            <x-ui.skeleton class="h-4 w-1/4" />
            <x-ui.skeleton class="h-4 w-1/5" />
            <x-ui.skeleton class="ms-auto h-4 w-16" />
        </div>
    @endforeach
    <span class="sr-only">{{ __('Loading…') }}</span>
</div>
text-block.blade.php Blade
<div class="w-full max-w-sm space-y-2" role="status" aria-busy="true" aria-label="{{ __('Loading paragraph') }}">
    <x-ui.skeleton shape="text" />
    <x-ui.skeleton shape="text" />
    <x-ui.skeleton shape="text" class="w-4/5" />
    <x-ui.skeleton shape="text" class="w-2/3" />
    <span class="sr-only">{{ __('Loading…') }}</span>
</div>

API

manifest knowledge + registry-derived coverage

Props

Props accepted by this component: name, type, default value and description.
Prop Type Default Description
shape mixed | null null Preset shape: text, circle, rect, rounded, or null for a bare rounded bar.
animated bool true Pulses the placeholder; false renders a static block.

Slots

  • default — Unused; the component has no visible default slot content.

Data slots

Stable hooks for CSS overrides and browser tests.

skeleton

Behavior

  • Renders a single bg-muted block sized and rounded per the shape preset; callers can override dimensions via the merged class attribute.
  • Composed layouts (card, list, table, stat) are several skeleton instances stacked with ordinary spacing utilities.
  • Declares registry capability flags: a11y, responsive, rtl, darkMode, localized.

Guidance

Loading feedback

Show that content or system work is in progress.

Use when

  • Use while content or system work is in progress so users understand the interface is responding.
  • Placeholding content that is still loading, so the layout does not jump once real content arrives.
  • Building a text, card, list, table, media, or stat placeholder from a preset shape, or composing a custom layout from bare bars.

Avoid when

  • Do not use indefinite loading indicators when you can show structure, progress, or a next step instead.
  • Prefer over a spinner when the layout is known and can be previewed without implying false progress.
  • Replacing content that has finished loading with no data; use empty instead.
  • Needing a labelled, announced busy indicator; use spinner or a loader component, since skeleton is aria-hidden.

Use instead

  • Skeleton for known layout
  • Determinate progress for measurable work

Anti-patterns

  • Indefinite loading with no progress or recovery
Anatomy
root
Theming hooks
skeleton

Accessibility

WCAG 2.2 AA Keyboard focus-visible RTL-ready Localized labels Dark mode
Keyboard
No component-owned keyboard interaction; native element behavior applies.
Focus
none
  • The root carries aria-hidden="true" since it has no meaningful content; announce the loading state separately, for example with a nearby spinner or a visually hidden status message.
  • The pulse animation respects prefers-reduced-motion automatically via motion-safe/motion-reduce utilities.
  • 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

Safe

Livewire can update this component through forwarded wire:* attributes.

Source

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

resources/views/components/ui/skeleton.blade.php Blade
@props([
    'shape' => null,
    'animated' => true,
])

@php
    // Convenience shapes — sensible default dimensions/radius that callers can
    // still override via the merged `class` attribute. `null` keeps the bare
    // bar (backward compatible with 1.0.0).
    $shapeClass = match ($shape) {
        'text' => 'h-4 w-full rounded-md',
        'circle' => 'size-12 rounded-full',
        'rect' => 'h-24 w-full rounded-none',
        'rounded' => 'h-24 w-full rounded-lg',
        default => 'rounded-md',
    };

    $animationClass = $animated ? 'motion-safe:animate-pulse motion-reduce:animate-none' : '';
@endphp

<div
    data-slot="skeleton"
    @if ($shape) data-shape="{{ $shape }}" @endif
    aria-hidden="true"
    {{ $attributes->merge(['class' => trim("$animationClass $shapeClass bg-muted")]) }}
></div>

Ownership & lifecycle

Owner, release state, review evidence and adoption for this item.
Owner
Platform UI (@JoshJML)
Current version
1.1.3
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