Book
A static 3D book mockup with a colored cover, spine and page edges that opens on hover. Wraps a title/author, a custom cover or arbitrary slot content, with an optional textured material finish.
Preview
<div class="flex flex-wrap items-end gap-10">
<x-ui.book title="The Pragmatic Blade" author="J. van Zeelt" color="primary" />
<x-ui.book title="Tokens & Themes" author="UI Press" color="secondary" thickness="32px" />
</div>
Installation
php artisan ui:add book
Registry contract
php artisan ui:add book
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.
-
resources/views/components/ui/book.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.
# Brok UI: Book (`book`)
A static 3D book mockup with a colored cover, spine and page edges that opens on hover. Wraps a title/author, a custom cover or arbitrary slot content, with an optional textured material finish.
Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.
## Install
```bash
php artisan ui:add book
```
## Usage
```blade
<div class="flex flex-wrap items-end gap-10">
<x-ui.book title="The Pragmatic Blade" author="J. van Zeelt" color="primary" />
<x-ui.book title="Tokens & Themes" author="UI Press" color="secondary" thickness="32px" />
</div>
```
## Props
- `title` (mixed|null, default `null`) — Book title shown on the front cover when no custom cover is given.
- `author` (mixed|null, default `null`) — Author name shown below the title on the front cover.
- `color` (string, default `primary`) — Cover color token: primary, secondary, destructive, accent, or muted.
- `thickness` (string, default `40px`) — CSS length for the book's spine and page-block depth.
- `cover` (mixed|null, default `null`) — Custom content replacing the default title/author cover face.
- `textured` (bool, default `false`) — Adds a decorative woven-cloth texture and sheen to the cover.
## Use when
- Use only when visual expression supports storytelling, brand differentiation, or a clear showcase moment.
- Presenting an ebook, course, or product as a 3D hardback-style cover, such as on a store or library page.
- Pairing a title and author with a colored cover, or supplying fully custom cover content through the cover slot.
## Avoid when
- Do not place heavy effects in task-dense workflows where clarity, speed, and readability matter more.
- The item is not book-shaped content, such as a video or audio track; use mockup or device instead.
## Anti-patterns
- Heavy effects in forms or task-dense workflows
- Motion that interferes with reading or focus
## Rules
- Use the `<brok:book>` tag (or `<x-ui.book>`) 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/book
- Registry JSON (files, props, contract): https://brokui.dev/r/open/book.json
Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.
Examples
<div class="flex flex-wrap items-end justify-center gap-8 p-4">
<x-ui.book title="The Token System" author="J. Marlow" color="primary" textured />
<x-ui.book title="Field Notes" author="A. Rivera" color="secondary" textured />
{{-- Arbitrary slot content inside the cover (content mode). --}}
<x-ui.book color="accent" textured>
<span class="text-2xl font-bold">JB</span>
<span class="mt-1 text-[0.65rem] uppercase tracking-widest opacity-80">Handbook</span>
</x-ui.book>
</div>
API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| title | mixed | null | null | Book title shown on the front cover when no custom cover is given. |
| author | mixed | null | null | Author name shown below the title on the front cover. |
| color | string | primary | Cover color token: primary, secondary, destructive, accent, or muted. |
| thickness | string | 40px | CSS length for the book's spine and page-block depth. |
| cover | mixed | null | null | Custom content replacing the default title/author cover face. |
| textured | bool | false | Adds a decorative woven-cloth texture and sheen to the cover. |
Slots
default—
Data slots
Stable hooks for CSS overrides and browser tests.
Behavior
- Renders a pure-CSS 3D book at a fixed rotation that straightens slightly on hover, motion-safe only; reduced motion keeps the static angled book.
- cover replaces the default title/author layout entirely when provided; otherwise title, author, and the slot render on the front face.
- textured adds a faint woven-cloth hatch and diagonal sheen to the cover, both drawn from currentColor so they theme automatically.
- Declares registry capability flags: a11y, responsive, rtl, darkMode, localized, reducedMotion.
Guidance
Support brand expression or a showcase moment.
Use when
- Use only when visual expression supports storytelling, brand differentiation, or a clear showcase moment.
- Presenting an ebook, course, or product as a 3D hardback-style cover, such as on a store or library page.
- Pairing a title and author with a colored cover, or supplying fully custom cover content through the cover slot.
Avoid when
- Do not place heavy effects in task-dense workflows where clarity, speed, and readability matter more.
- The item is not book-shaped content, such as a video or audio track; use mockup or device instead.
Use instead
- Static content-first presentation
Anti-patterns
- Heavy effects in forms or task-dense workflows
- Motion that interferes with reading or focus
- Anatomy
- Theming hooks
Accessibility
- Keyboard
- No component-owned keyboard interaction; native element behavior applies.
- Focus
none
- The back cover, spine, page edges, and decorative textures are all aria-hidden; only the title, author, or custom cover content is exposed to assistive tech.
- Semantic HTML and a stable
data-slotattribute for styling and scripting hooks. - Focus-visible rings use the
ringtoken, 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 underdir="rtl"— flip the preview to RTL to confirm. - Dark mode uses the same semantic tokens under the
darkclass; high contrast follows forced-color system tokens.
Livewire
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.
@props([
'title' => null,
'author' => null,
'color' => 'primary',
'thickness' => '40px',
'cover' => null,
// When true the cover gains a subtle token-only "material" finish — a faint
// woven texture (currentColor weave) plus a soft diagonal sheen — so the book
// reads as cloth/hardback rather than flat colour. Purely decorative.
'textured' => false,
])
@php
// Pure-CSS 3D book: a perspective wrapper holds a preserve-3d inner element
// that sits at rotateY(-25deg) and opens to 0deg on hover (motion-safe only,
// so reduced-motion users see a static, slightly-angled book).
$coverBg = match ($color) {
'secondary' => 'bg-secondary',
'destructive' => 'bg-destructive',
'accent' => 'bg-accent',
'muted' => 'bg-muted',
default => 'bg-primary',
};
// The cover label must use the foreground that pairs with its background,
// otherwise a light cover (secondary/muted) gets unreadable white text.
$coverText = match ($color) {
'secondary' => 'text-secondary-foreground',
'destructive' => 'text-destructive-foreground',
'accent' => 'text-accent-foreground',
'muted' => 'text-muted-foreground',
default => 'text-primary-foreground',
};
// The 3D build: the front cover sits on the z=0 plane, the back cover
// `thickness` behind it, the page block hangs off the cover's right edge
// turned 90° into the screen, and the spine hangs off the left edge the
// other way. Everything is anchored by transform-origin, so `thickness`
// is the only length involved.
$negThickness = 'calc('.$thickness.' * -1)';
@endphp
<div
data-slot="book"
{{ $attributes->merge(['class' => 'inline-block [perspective:1200px]']) }}
>
<div
class="group/book relative aspect-[3/4] w-48 [transform-style:preserve-3d] [transform:rotateY(-28deg)] transition-transform duration-500 ease-out motion-safe:hover:[transform:rotateY(-14deg)_translateY(-4px)] motion-reduce:transition-none"
>
{{-- Back cover, one thickness behind the front. --}}
<div
class="absolute inset-0 rounded-r-md rounded-l-sm {{ $coverBg }} brightness-75"
style="transform: translateZ({{ $negThickness }});"
aria-hidden="true"
></div>
{{-- Page block: hangs off the cover's right edge, turned into the
screen; fine hairlines read as the fore-edge of the leaves. --}}
<div
class="absolute inset-y-1 left-full origin-left rounded-r-sm bg-muted bg-[repeating-linear-gradient(90deg,transparent_0,transparent_2px,var(--color-border)_2px,var(--color-border)_3px)]"
style="width: {{ $thickness }}; transform: rotateY(90deg) translateX(-1px);"
aria-hidden="true"
></div>
{{-- Spine: hangs off the left edge, turned the other way. --}}
<div
class="absolute inset-y-0 left-0 origin-left rounded-l-sm {{ $coverBg }} brightness-90"
style="width: {{ $thickness }}; transform: rotateY(90deg);"
aria-hidden="true"
></div>
{{-- Front cover face --}}
<div class="absolute inset-0 isolate flex flex-col items-center justify-center gap-1 overflow-hidden rounded-r-md rounded-l-sm border border-foreground/10 p-5 text-center shadow-xl {{ $coverBg }} {{ $coverText }} [backface-visibility:hidden]">
{{-- Hinge: a soft dark crease along the spine edge of the cover. --}}
<div class="pointer-events-none absolute inset-y-0 left-0 -z-10 w-3 bg-gradient-to-r from-foreground/20 to-transparent" aria-hidden="true"></div>
@if ($textured)
{{-- Woven cloth weave: a faint diagonal currentColor hatch (currentColor
resolves to the cover's own foreground token, so it themes for free). --}}
<div
class="pointer-events-none absolute inset-0 -z-10 opacity-[0.06] bg-[repeating-linear-gradient(45deg,currentColor_0,currentColor_1px,transparent_1px,transparent_4px)]"
aria-hidden="true"
></div>
{{-- Soft diagonal sheen so the material catches light at the spine edge. --}}
<div
class="pointer-events-none absolute inset-0 -z-10 bg-gradient-to-br from-foreground/10 via-transparent to-background/10 mix-blend-overlay"
aria-hidden="true"
></div>
@endif
@if ($cover)
{{ $cover }}
@else
@if (filled($title))
<span class="text-balance text-lg font-semibold leading-tight">{{ $title }}</span>
@endif
@if (filled($author))
<span class="text-xs opacity-80">{{ $author }}</span>
@endif
{{ $slot }}
@endif
</div>
</div>
</div>
Ownership & lifecycle
Owner, release state, review evidence and adoption for this item.
- Owner
- Platform UI (@JoshJML)
- Current version
-
1.2.2 - 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