Skip to content
Brok UI

Loading…

No results

Card Stack

Open source

Stacked card decks — a click-to-cycle layered deck, a pointer-draggable swipe deck, and an orbiting 3D ring.

Version
v1.0.4
Stability
stable
License
MIT
Related
Layered Stack
Draggable 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.

Preview

UI
TW
PHP
JS
CSS
DB
Core
previews.components.orbit-card-stack.default.blade.php Blade
<x-ui.orbit-card-stack class="my-8">
    <span class="font-semibold text-foreground">Core</span>
</x-ui.orbit-card-stack>

Installation

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

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

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

manifest knowledge + registry-derived coverage

Props

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

orbit-card-stack

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

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.
  • 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
root
Theming hooks
orbit-card-stack

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 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-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/orbit-card-stack.blade.php Blade
@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