Skip to content
Brok UI

Loading…

No results

Text Effects

Open source

Animated and decorative text treatments — staged reveals, typing, particle and cursor-repel effects, and clipped gradients.

Version
v1.0.2
Stability
stable
License
MIT
Related
Text Animate
Typewriter
Kinetic Text Reveal
Letter Cascade
Particle Typography
Text Repel
Text Gradient
Encrypted Text
8 of 9

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.

Preview

previews.components.circular-text.default.blade.php 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>

Installation

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

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

circular-text.md
# 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

manifest knowledge + registry-derived coverage

Props

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

circular-text

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

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.
  • 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
root
Theming hooks
circular-text

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 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-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/circular-text.blade.php Blade
@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