Holo Card
A 3D holographic profile card with pointer-tracked tilt, a moving iridescent token-gradient sheen and click-to-flip back face.
Preview
Ada Lovelace
Founding Engineer
Joined 2026
Ada Lovelace
Building the first analytical engine for the web. Ask me about loops, looms and good API design.
Tap to flip back<div class="w-full flex justify-center py-6">
<x-ui.holo-card
name="Ada Lovelace"
role="Founding Engineer"
badge="Member"
primary-id="No. 001"
secondary-info="Joined 2026"
description="Building the first analytical engine for the web. Ask me about loops, looms and good API design."
/>
</div>
Installation
php artisan ui:add holo-card
Note
This component ships an Alpine behavior module at
resources/js/ui/holo-card.js. Import it once from your bundle so it registers on alpine:init:
import './holo-card.js';
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: Holo Card (`holo-card`)
A 3D holographic profile card with pointer-tracked tilt, a moving iridescent token-gradient sheen and click-to-flip back face.
Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.
## Install
```bash
php artisan ui:add holo-card
```
## Usage
```blade
<div class="w-full flex justify-center py-6">
<x-ui.holo-card
name="Ada Lovelace"
role="Founding Engineer"
badge="Member"
primary-id="No. 001"
secondary-info="Joined 2026"
description="Building the first analytical engine for the web. Ask me about loops, looms and good API design."
/>
</div>
```
## Props
- `name` (string, default `Ada Lovelace`) — Person's name shown on the front and back faces.
- `role` (string, default `Founding Engineer`) — Role or title shown under the name on the front face.
- `avatar` (mixed|null, default `null`) — Image URL for the avatar; falls back to initials derived from name.
- `badge` (string, default `Member`) — Label text shown in the badge chip on the front face.
- `primaryId` (string, default `No. 001`) — Identifier text, such as a member number, shown opposite the badge.
- `secondaryInfo` (string, default `Joined 2026`) — Supporting text, such as a join date, shown under the role.
- `description` (string, default `Building the first analytical engine for the web. Ask me about loops, looms and good API design.`) — Longer text revealed on the back face when the card is flipped.
- `flippable` (bool, default `true`) — Allows clicking or pressing Enter/Space to flip the card and reveal the back face.
- `sparkles` (bool, default `true`) — Adds small pulsing sparkle dots over the front face.
## Use when
- Use only when visual expression supports storytelling, brand differentiation, or a clear showcase moment.
- Presenting a profile, membership, or trading-card-style identity with a playful holographic tilt and shimmer effect.
- Revealing extra detail, such as a bio, on the back of the card through a click-to-flip interaction.
## Avoid when
- Do not place heavy effects in task-dense workflows where clarity, speed, and readability matter more.
- The card needs to stay flat and inert in a dense list of many profiles; use avatar or card instead.
## Anti-patterns
- Heavy effects in forms or task-dense workflows
- Motion that interferes with reading or focus
## Rules
- Use the `<brok:holo-card>` tag (or `<x-ui.holo-card>`) 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/holo-card
- Registry JSON (files, props, contract): https://brokui.dev/r/open/holo-card.json
Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.
API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| name | string | Ada Lovelace | Person's name shown on the front and back faces. |
| role | string | Founding Engineer | Role or title shown under the name on the front face. |
| avatar | mixed | null | null | Image URL for the avatar; falls back to initials derived from name. |
| badge | string | Member | Label text shown in the badge chip on the front face. |
| primaryId | string | No. 001 | Identifier text, such as a member number, shown opposite the badge. |
| secondaryInfo | string | Joined 2026 | Supporting text, such as a join date, shown under the role. |
| description | string | Building the first analytical engine for the web... | Longer text revealed on the back face when the card is flipped. |
| flippable | bool | true | Allows clicking or pressing Enter/Space to flip the card and reveal the back face. |
| sparkles | bool | true | Adds small pulsing sparkle dots over the front face. |
Slots
default—
Data slots
Stable hooks for CSS overrides and browser tests.
Behavior
- Tracks the pointer over the card to tilt it in 3D and slide an iridescent gradient sheen toward the cursor; both are skipped under prefers-reduced-motion.
- Clicking, or pressing Enter or Space when focused, flips the card to its back face when flippable is true, revealing the description.
- Installs a JavaScript behavior module when the registry item includes resources/js/ui files.
- Declares registry capability flags: a11y, responsive, rtl, darkMode, localized, interactive.
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 a profile, membership, or trading-card-style identity with a playful holographic tilt and shimmer effect.
- Revealing extra detail, such as a bio, on the back of the card through a click-to-flip interaction.
Avoid when
- Do not place heavy effects in task-dense workflows where clarity, speed, and readability matter more.
- The card needs to stay flat and inert in a dense list of many profiles; use avatar or card 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
- Enter Space
- Focus
managed
- When flippable, the flip control is a role="button" with aria-pressed reflecting the flip state and is reachable and operable by keyboard.
- 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
Add a stable wire:key when Livewire can reorder this interactive component.
<div wire:key="holo-card-{{ $record->id }}">
<div class="w-full flex justify-center py-6">
<x-ui.holo-card
name="Ada Lovelace"
role="Founding Engineer"
badge="Member"
primary-id="No. 001"
secondary-info="Joined 2026"
description="Building the first analytical engine for the web. Ask me about loops, looms and good API design."
/>
</div>
</div>
Source
The exact, editable file ui:add writes
into your app. Previews render this same code; there are no preview-only components.
@props([
'name' => 'Ada Lovelace',
'role' => 'Founding Engineer',
'avatar' => null,
'badge' => 'Member',
'primaryId' => 'No. 001',
'secondaryInfo' => 'Joined 2026',
'description' => 'Building the first analytical engine for the web. Ask me about loops, looms and good API design.',
'flippable' => true,
'sparkles' => true,
])
{{--
Holo card — a 3D holographic profile card. An inline Alpine controller tracks
the pointer to tilt the card and slide a token-gradient iridescent sheen
across it; clicking flips it (when `flippable`) to reveal the description on
the back. All listeners are bound to the element via x-on (Alpine removes them
on teardown — no leaks) and the tilt is skipped under prefers-reduced-motion.
Colours are semantic tokens only; the holo shimmer is a moving primary→info→
secondary gradient, never raw hues.
--}}
<div
data-slot="holo-card"
x-data="{
rx: 0, ry: 0, gx: 50, gy: 50, flipped: false,
reduced: false,
init() { this.reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches; },
move(e) {
if (this.reduced) return;
const r = this.$el.getBoundingClientRect();
const px = (e.clientX - r.left) / r.width;
const py = (e.clientY - r.top) / r.height;
this.ry = (px - 0.5) * 16;
this.rx = (0.5 - py) * 16;
this.gx = Math.round(px * 100);
this.gy = Math.round(py * 100);
},
reset() { this.rx = 0; this.ry = 0; this.gx = 50; this.gy = 50; },
}"
x-on:mousemove="move($event)"
x-on:mouseleave="reset()"
{{ $attributes->merge(['class' => 'group/holo inline-block w-72 max-w-full select-none [perspective:1000px]']) }}
>
<div
@if ($flippable) x-on:click="flipped = !flipped" role="button" tabindex="0" x-on:keydown.enter.prevent="flipped = !flipped" x-on:keydown.space.prevent="flipped = !flipped" aria-pressed="false" x-bind:aria-pressed="flipped.toString()" @endif
class="relative aspect-[5/7] w-full rounded-lg transition-transform duration-200 ease-out [transform-style:preserve-3d] motion-reduce:transition-none @if ($flippable) cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background @endif"
x-bind:style="{ transform: `rotateX(${rx}deg) rotateY(${flipped ? ry + 180 : ry}deg)` }"
>
{{-- Front face --}}
<div class="absolute inset-0 flex flex-col overflow-hidden rounded-lg border border-border bg-gradient-to-br from-primary/15 via-card to-secondary/15 p-6 shadow-xl [backface-visibility:hidden]">
{{-- Holographic foil: the five chart hues in a conic sweep that turns
with the pointer, overlaid on the card; a soft glare follows the
pointer on top. Both are token-driven (chart-* / background). --}}
<div
class="pointer-events-none absolute inset-0 opacity-35 mix-blend-overlay transition-opacity duration-300 [background-image:conic-gradient(from_var(--holo-angle),var(--color-chart-1),var(--color-chart-2),var(--color-chart-3),var(--color-chart-4),var(--color-chart-5),var(--color-chart-1))] motion-reduce:transition-none"
x-bind:style="{ '--holo-angle': (gx * 3.6) + 'deg' }"
aria-hidden="true"
></div>
<div
class="pointer-events-none absolute inset-0 opacity-0 mix-blend-soft-light transition-opacity duration-300 group-hover/holo:opacity-100 [background-image:radial-gradient(circle_at_var(--glare-x)_var(--glare-y),var(--color-background)_0%,transparent_55%)] motion-reduce:transition-none"
x-bind:style="{ '--glare-x': gx + '%', '--glare-y': gy + '%' }"
aria-hidden="true"
></div>
@if ($sparkles)
<div class="pointer-events-none absolute inset-0" aria-hidden="true">
<span class="absolute left-6 top-10 size-1 rounded-full bg-foreground/40 motion-safe:animate-pulse"></span>
<span class="absolute right-8 top-16 size-2 rounded-full bg-foreground/30 motion-safe:animate-pulse [animation-delay:300ms]"></span>
<span class="absolute left-10 bottom-16 size-1 rounded-full bg-foreground/40 motion-safe:animate-pulse [animation-delay:600ms]"></span>
</div>
@endif
<div class="relative flex items-center justify-between">
<x-ui.badge variant="outline" size="sm" class="bg-background/60 backdrop-blur">{{ $badge }}</x-ui.badge>
<span class="text-xs font-medium text-muted-foreground">{{ $primaryId }}</span>
</div>
<div class="relative mt-auto flex flex-col items-start gap-3">
@php($initials = mb_strtoupper(implode('', array_map(fn ($w) => mb_substr($w, 0, 1), array_slice(preg_split('/\s+/', trim($name)) ?: [], 0, 2)))))
<x-ui.avatar size="lg" :src="$avatar" :alt="$name" :fallback="$initials" class="ring-2 ring-background/70" />
<div>
<p class="text-lg font-bold leading-tight">{{ $name }}</p>
<p class="text-sm text-muted-foreground">{{ $role }}</p>
</div>
<p class="text-xs text-muted-foreground">{{ $secondaryInfo }}</p>
</div>
</div>
{{-- Back face --}}
@if ($flippable)
<div class="absolute inset-0 flex flex-col overflow-hidden rounded-lg border border-border bg-gradient-to-br from-secondary/20 via-card to-primary/15 p-6 shadow-xl [backface-visibility:hidden] [transform:rotateY(180deg)]">
<div class="pointer-events-none absolute inset-0 bg-gradient-to-bl from-info/30 via-primary/20 to-secondary/30 opacity-50 mix-blend-overlay" aria-hidden="true"></div>
<p class="relative text-sm font-semibold">{{ $name }}</p>
<p class="relative mt-3 text-sm leading-relaxed text-muted-foreground">{{ $description }}</p>
<span class="relative mt-auto text-xs text-muted-foreground">{{ __('Tap to flip back') }}</span>
</div>
@endif
</div>
</div>
Ownership & lifecycle
Owner, release state, review evidence and adoption for this item.
- Owner
- Platform UI (@JoshJML)
- Current version
-
1.1.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