Card Stack
Stacked card decks — a click-to-cycle layered deck, a pointer-draggable swipe deck, and an orbiting 3D ring.
Orbit Card Stack — Items (avatars, badges or initials) revolve on a circular path around a center slot. Each item counter-rotates so it stays upright as the ring spins, with pause-on-hover and a static reduced-motion arrangement.
Preview
<x-ui.orbit-card-stack class="my-8">
<span class="font-semibold text-foreground">Core</span>
</x-ui.orbit-card-stack>
Installation
php artisan ui:add orbit-card-stack
Registry contract
php artisan ui:add orbit-card-stack
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/orbit-card-stack.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: Orbit Card Stack (`orbit-card-stack`)
Items (avatars, badges or initials) revolve on a circular path around a center slot. Each item counter-rotates so it stays upright as the ring spins, with pause-on-hover and a static reduced-motion arrangement.
Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.
## Install
```bash
php artisan ui:add orbit-card-stack
```
## Usage
```blade
<x-ui.orbit-card-stack class="my-8">
<span class="font-semibold text-foreground">Core</span>
</x-ui.orbit-card-stack>
```
## Props
- `items` (array, default `[]`) — Items revolving on the ring; each is a short label or {label, bg}.
- `radius` (string, default `7rem`) — Orbit radius, as a CSS length, from the centre slot to each item.
- `duration` (int, default `20`) — Seconds for one full revolution of the ring.
- `reverse` (bool, default `false`) — Reverses the spin direction when true.
- `pauseOnHover` (bool, default `true`) — Pauses the ring's animation on hover when true.
## Use when
- Use only when visual expression supports storytelling, brand differentiation, or a clear showcase moment.
- Showing a small set of avatars, badges or initials revolving around a central logo or label, such as an integrations or team-members display.
- Wanting a purely decorative, always-on animated ring rather than a navigable carousel or deck.
## Avoid when
- Do not place heavy effects in task-dense workflows where clarity, speed, and readability matter more.
- The items need to be individually selectable or navigable; this component has no active-item state, prev/next, or keyboard navigation, so use carousel or one of the card-stack components instead.
## Anti-patterns
- Heavy effects in forms or task-dense workflows
- Motion that interferes with reading or focus
## Rules
- Use the `<brok:orbit-card-stack>` tag (or `<x-ui.orbit-card-stack>`) 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/orbit-card-stack
- Registry JSON (files, props, contract): https://brokui.dev/r/open/orbit-card-stack.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 |
|---|---|---|---|
| items | array | [] | Items revolving on the ring; each is a short label or {label, bg}. |
| radius | string | 7rem | Orbit radius, as a CSS length, from the centre slot to each item. |
| duration | int | 20 | Seconds for one full revolution of the ring. |
| reverse | bool | false | Reverses the spin direction when true. |
| pauseOnHover | bool | true | Pauses the ring's animation on hover when true. |
Slots
default— Content for the fixed centre slot, such as a logo or label.
Data slots
Stable hooks for CSS overrides and browser tests.
Behavior
- Items revolve on a circular path around a fixed centre slot; each item counter-rotates in place so it stays upright as the ring spins, and the whole ring pauses on hover when pauseOnHover is true.
- reverse flips the spin direction; radius and duration control the orbit size and speed.
- 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.
- Showing a small set of avatars, badges or initials revolving around a central logo or label, such as an integrations or team-members display.
- Wanting a purely decorative, always-on animated ring rather than a navigable carousel or deck.
Avoid when
- Do not place heavy effects in task-dense workflows where clarity, speed, and readability matter more.
- The items need to be individually selectable or navigable; this component has no active-item state, prev/next, or keyboard navigation, so use carousel or one of the card-stack components 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 animation is motion-safe only: under prefers-reduced-motion the ring is static, with every item arranged evenly around the circle, upright and fully visible rather than mid-spin.
- The orbiting items are decorative content around the centre slot; give the centre slot's content its own accessible name since the orbit itself carries no semantics.
- 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([
'items' => [],
'radius' => '7rem',
'duration' => 20,
'reverse' => false,
'pauseOnHover' => true,
])
@php
// A ring of small chips orbits a fixed center slot. The whole ring spins via
// the `ui-orbit-spin` keyframe (0→360); every chip counter-spins via
// `ui-orbit-counter` (0→-360, same duration) so it stays upright while it
// travels round the circle. Each chip is also placed at its own angle on the
// path with `rotate(θ) translateX(--radius) rotate(-θ)` (the `.ui-orbit-item`
// helper), where θ = i * 360/count comes from PHP via `--angle`.
//
// All motion is motion-safe only: reduced-motion users get the ring static,
// with the chips arranged evenly around the circle, upright and fully visible.
$items = empty($items) ? ['UI', 'TW', 'PHP', 'JS', 'CSS', 'DB'] : $items;
$count = count($items);
$dur = is_numeric($duration) ? $duration.'s' : $duration;
$pause = $pauseOnHover ? 'group-hover:[animation-play-state:paused]' : '';
// Animation utilities written as contiguous literals (all four variants) so
// Tailwind's JIT can see and emit them. Splicing the keyframe name into
// animate-[…] hides the class from the scanner, so the utility is never
// generated and the ring/chips sit static.
$ringAnim = ($reverse
? 'motion-safe:animate-[ui-orbit-spin-rev_var(--dur)_linear_infinite]'
: 'motion-safe:animate-[ui-orbit-spin_var(--dur)_linear_infinite]').' '.$pause;
$chipAnim = ($reverse
? 'motion-safe:animate-[ui-orbit-counter-rev_var(--dur)_linear_infinite]'
: 'motion-safe:animate-[ui-orbit-counter_var(--dur)_linear_infinite]').' '.$pause;
@endphp
{{-- The chips and guide ring are position:absolute (out of flow), so the root
must be sized explicitly to the orbit — otherwise it collapses to the
center slot and the guide ring's margin-auto centering drifts off-centre.
place-content-center keeps the centred slot centred in the sized box. --}}
<div
data-slot="orbit-card-stack"
style="--dur:{{ $dur }}; --radius:{{ $radius }}; width:calc(var(--radius) * 2 + 2.5rem); height:calc(var(--radius) * 2 + 2.5rem);"
{{ $attributes->merge(['class' => 'group relative isolate grid place-items-center place-content-center']) }}
>
{{-- Faint concentric guide ring. --}}
<div
aria-hidden="true"
class="ui-orbit-guide pointer-events-none rounded-full border border-border/60"
></div>
{{-- Spinning ring of orbiting chips. --}}
<div class="ui-orbit-ring pointer-events-none absolute inset-0 {{ $ringAnim }}">
@foreach ($items as $i => $item)
@php
$label = is_array($item) ? ($item['label'] ?? '') : $item;
$bg = is_array($item) ? ($item['bg'] ?? 'bg-card') : 'bg-card';
$angle = $i * (360 / $count);
@endphp
<div class="ui-orbit-item" style="--angle:{{ $angle }}deg;">
<div class="pointer-events-auto flex {{ $chipAnim }}">
<span
class="flex size-10 select-none items-center justify-center rounded-full border border-border {{ $bg }} text-xs font-medium text-card-foreground shadow-sm"
>{{ $label }}</span>
</div>
</div>
@endforeach
</div>
{{-- Center slot (logo/label). --}}
<div
class="relative z-10 flex size-16 items-center justify-center rounded-full border border-border bg-card text-card-foreground shadow-sm"
>
{{ $slot }}
</div>
</div>
Ownership & lifecycle
Owner, release state, review evidence and adoption for this item.
- Owner
- Platform UI (@JoshJML)
- Current version
-
1.0.4 - 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