Skip to content
Brok UI

Loading…

No results

Book

Open source

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.

Version
v1.2.2
Stability
stable
License
MIT
Related
Mockup
Device Mockup
iPhone Mockup

Preview

The Pragmatic Blade J. van Zeelt
Tokens & Themes UI Press
previews.components.book.default.blade.php 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>

Installation

terminal
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.

  • blade 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.

book.md
# 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

textured.blade.php Blade
<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

manifest knowledge + registry-derived coverage

Props

Props accepted by this component: name, type, default value and description.
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.

book

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

Expressive and showcase effect

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
root
Theming hooks
book

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 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-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/book.blade.php Blade
@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