Skip to content
Brok UI

Loading…

No results

Holo Card

Open source

A 3D holographic profile card with pointer-tracked tilt, a moving iridescent token-gradient sheen and click-to-flip back face.

Version
v1.1.2
Stability
stable
License
MIT
Related
Tilt Card
Card
Scroll Split Card

Preview

Member No. 001
AL Ada Lovelace

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
previews.components.holo-card.default.blade.php 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>

Installation

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

resources/js/ui/index.js JS
import './holo-card.js';

Registry contract

php artisan ui:add holo-card 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/holo-card.blade.php
Registry dependencies
avatar badge
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.

holo-card.md
# 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

manifest knowledge + registry-derived coverage

Props

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

holo-card

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

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 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
root
Theming hooks
holo-card

Accessibility

WCAG 2.2 AA Keyboard focus-visible RTL-ready Localized labels Dark mode
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-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="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.

resources/views/components/ui/holo-card.blade.php Blade
@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