Text Effects
Animated and decorative text treatments — staged reveals, typing, particle and cursor-repel effects, and clipped gradients.
Preview
<div class="flex min-h-56 w-full items-center justify-center rounded-lg border border-border bg-card p-8">
<x-ui.circular-text text="CIRCULAR • TEXT • SPIN • " :radius="70" :duration="12" class="text-foreground" />
</div>
Installation
php artisan ui:add circular-text
Registry contract
php artisan ui:add circular-text
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/circular-text.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: Circular Text (`circular-text`)
A string laid out around a circle, with the whole ring rotating continuously. Pure CSS, motion-gated, with optional reverse spin and pause-on-hover.
Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.
## Install
```bash
php artisan ui:add circular-text
```
## Usage
```blade
<div class="flex min-h-56 w-full items-center justify-center rounded-lg border border-border bg-card p-8">
<x-ui.circular-text text="CIRCULAR • TEXT • SPIN • " :radius="70" :duration="12" class="text-foreground" />
</div>
```
## Props
- `text` (string, default `CIRCULAR • TEXT • `) — The repeating ring text; each character is laid out around the circle.
- `radius` (int, default `80`) — Circle radius in pixels, also half the square box edge; clamped between 16 and 400.
- `duration` (int, default `12`) — Seconds for one full revolution.
- `reverse` (bool, default `false`) — Spins the ring counter-clockwise instead of clockwise.
- `pauseOnHover` (bool, default `true`) — Freezes the spin while the pointer is over the ring.
## Use when
- Use only when visual expression supports storytelling, brand differentiation, or a clear showcase moment.
- Laying out a short repeating string, such as a badge or stamp label, around a continuously rotating ring.
- Wanting a decorative spinning-text effect with optional reverse direction and pause-on-hover.
## Avoid when
- Do not place heavy effects in task-dense workflows where clarity, speed, and readability matter more.
- Needing readable, static text; a spinning ring is hard to read and should stay decorative or brief.
- Animating individual letters in a straight line; use text-animate, kinetic-text-reveal, or letter-cascade instead.
## Anti-patterns
- Heavy effects in forms or task-dense workflows
- Motion that interferes with reading or focus
## Rules
- Use the `<brok:circular-text>` tag (or `<x-ui.circular-text>`) 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/circular-text
- Registry JSON (files, props, contract): https://brokui.dev/r/open/circular-text.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 |
|---|---|---|---|
| text | string | CIRCULAR • TEXT • | The repeating ring text; each character is laid out around the circle. |
| radius | int | 80 | Circle radius in pixels, also half the square box edge; clamped between 16 and 400. |
| duration | int | 12 | Seconds for one full revolution. |
| reverse | bool | false | Spins the ring counter-clockwise instead of clockwise. |
| pauseOnHover | bool | true | Freezes the spin while the pointer is over the ring. |
Slots
default— Unused; the component has no visible default slot content.
Data slots
Stable hooks for CSS overrides and browser tests.
Behavior
- Each character is an absolutely positioned span rotated to its angle around the box centre; the whole layer carries the continuous rotation.
- Reduced motion renders a static ring instead of animating; pauseOnHover freezes the spin on hover when enabled.
- Declares registry capability flags: a11y, responsive, rtl, darkMode, localized.
Guidance
Support brand expression or a showcase moment.
Use when
- Use only when visual expression supports storytelling, brand differentiation, or a clear showcase moment.
- Laying out a short repeating string, such as a badge or stamp label, around a continuously rotating ring.
- Wanting a decorative spinning-text effect with optional reverse direction and pause-on-hover.
Avoid when
- Do not place heavy effects in task-dense workflows where clarity, speed, and readability matter more.
- Needing readable, static text; a spinning ring is hard to read and should stay decorative or brief.
- Animating individual letters in a straight line; use text-animate, kinetic-text-reveal, or letter-cascade 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 root carries role="img" and an aria-label of the full text, so screen readers get the string as one sentence instead of scattered letters.
- Individual character spans are aria-hidden since the label already conveys the text.
- 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([
// The repeating ring text. Each character is laid out around the circle.
'text' => 'CIRCULAR • TEXT • ',
// Circle radius in px — also half the square box edge.
'radius' => 80,
// Seconds for one full revolution.
'duration' => 12,
// Spin counter-clockwise.
'reverse' => false,
// Freeze the spin while the pointer is over the ring.
'pauseOnHover' => true,
])
@php
// Spinning circular text (chanhdai port). The string is laid out around a
// circle: each character is an absolutely-positioned <span> rotated to its
// angle and pushed out by the radius, while the container layer carries the
// continuous rotation. Pure CSS (no JS); the motion lives in the scoped
// <style> below and touches ONLY `transform` (never colour), so the design
// tokens stay the single source of truth. Reduced-motion users get a static
// ring.
//
// mb_str_split keeps multibyte glyphs (the bullet •, accents, CJK) intact.
$chars = mb_str_split($text);
$count = max(1, count($chars));
// Clamp inputs so a stray prop can't paint a giant or jittery ring.
$radius = max(16, min(400, (int) $radius));
$duration = max(1, (float) $duration);
// Box edge = diameter. Inline standard width/height (real CSS properties,
// NOT a Tailwind arbitrary-class splice the JIT can't see).
$box = $radius * 2;
@endphp
{{-- Keyframes carry NO colour; reduced-motion users get a static ring. --}}
<style>
@keyframes ui-circular-spin {
to { transform: rotate(360deg); }
}
@keyframes ui-circular-spin-reverse {
to { transform: rotate(-360deg); }
}
[data-slot="circular-text"] .ui-circular-text-spin {
animation-name: ui-circular-spin;
animation-timing-function: linear;
animation-iteration-count: infinite;
transform-origin: center;
}
[data-slot="circular-text"][data-reverse="true"] .ui-circular-text-spin {
animation-name: ui-circular-spin-reverse;
}
[data-slot="circular-text"][data-pause-on-hover="true"]:hover .ui-circular-text-spin {
animation-play-state: paused;
}
@media (prefers-reduced-motion: reduce) {
[data-slot="circular-text"] .ui-circular-text-spin {
animation: none;
}
}
</style>
<div
data-slot="circular-text"
data-reverse="{{ $reverse ? 'true' : 'false' }}"
data-pause-on-hover="{{ $pauseOnHover ? 'true' : 'false' }}"
role="img"
aria-label="{{ $text }}"
{{ $attributes->merge(['class' => 'text-foreground inline-block']) }}
>
{{-- Square box sized to the diameter (standard inline width/height). --}}
<div class="relative" style="width: {{ $box }}px; height: {{ $box }}px;">
{{-- The spinning layer. Revolution duration is a real CSS property,
NOT spliced into a utility class. --}}
<div
class="ui-circular-text-spin absolute inset-0"
style="animation-duration: {{ $duration }}s;"
>
@foreach ($chars as $i => $char)
@php
// Fixed numeric angle per glyph — written into a standard
// inline transform, never into a Tailwind class.
$angle = $i / $count * 360;
@endphp
{{-- Each glyph rides a cell that FILLS the box (inset-0), so its
rotation pivot is the box centre. `justify-center` puts the
glyph at 12 o'clock; the box edge sits exactly `radius` from
the centre, so the ring is always centred regardless of glyph
width — no fragile per-glyph translate maths. --}}
<span
aria-hidden="true"
class="absolute inset-0 flex justify-center"
style="transform: rotate({{ $angle }}deg);"
>
<span class="text-sm font-semibold uppercase leading-none">{{ $char }}</span>
</span>
@endforeach
</div>
</div>
</div>
Ownership & lifecycle
Owner, release state, review evidence and adoption for this item.
- Owner
- Platform UI (@JoshJML)
- Current version
-
1.0.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