registry
@automindzsheets 01–77shadcn 4.x
SHEET 47

Circular Text

component / motion

Text set around a circle — the rotating badge, the seal, the "scroll to explore" ring — as one <textPath> rather than one rotated span per character, so it still selects, searches and translates.

type
component
files
2
registry deps
none
npm deps
none

installs

  • components/automindz/circular-text.tsx
  • styles/automindz-circular.css
  • One <textPath>, not one rotated span per glyph — it selects, searches and translates
  • Anticlockwise flips the path's sweep, never the string
  • Radius derived from the box and the type, so there is no second measurement to disagree
  • aria-hidden by default; label swaps the repeated phrase for one role=img
  • Import styles/automindz-circular.css once in your layout

The stylesheet is not optional: add import "@/styles/automindz-circular.css" to your root layout once, or nothing turns. Skipping it is safe and never blank — the phrase is still set all the way around its circle, at the right radius, evenly spaced and legible, which is also the prefers-reduced-motion path, and **a still seal is the point of a seal**. text has no default (<CircularText text="Ship faster" />); there is no config file, because a phrase is not a business-shaped list. The centre slot is children and does **not** turn with the ring — the two are separate layers, so <CircularText><Ripple>…</Ripple></CircularText> composes and so does the reverse, and the box grows rather than clipping if the centre slot is bigger than the badge. There is no radius prop: it is derived from size and fontSize, because a radius beside a size is the same measurement twice and the symptom of the two disagreeing is text shaved off the top of its own badge. The repeat count is derived from an *estimated* average glyph advance, but the fit is not — the <textPath> carries textLength equal to the circumference with lengthAdjust="spacing", so the ring closes exactly whatever the estimate said and a bad estimate costs tracking, never a gap and never an overlap; pass repeat when you want it exact. **The ring is aria-hidden by default**: SVG <text> is real text, so a seal repeating its phrase would otherwise be announced two or three times in every screen reader and show up that many times in find-in-page. Pass label only when the badge says something the page does not — it swaps the subtree for one role="img" under your name. fontSize is in viewBox units out of 100, not px, and lengthAdjust absorbs any inherited letter-spacing, so set the real size with size and expect tracking-* utilities to be inert. Dial: --automindz-circular-duration (default 24s; much under about 6s it stops reading as a seal and starts reading as a loading spinner).

Variations

one state per section

01

The seal, with a mark in the middle

Select the ring and drag across it: it comes out as one string, in order, because this is a single <textPath> and not twenty-eight rotated spans. Find-in-page matches it too. The centre slot does not turn with the ring — the two are separate layers, and a seal whose mark spins with it is a spinning logo, which is a different and much worse thing.

fluid
AutoMindz Solutions
02

Anticlockwise, offset a quarter turn

The path's sweep is flipped, not the string — the glyphs read the right way round and the DOM is unchanged, where reversing the text would give correctly ordered glyphs that are each individually mirrored and a phrase no screen reader could read. The turn follows the path, so text set anticlockwise never slides backwards through itself. startOffset={25} is a quarter turn measured along the phrase, so on this badge it starts at nine o'clock rather than twelve — the same number would start a clockwise badge at three.

fluid
03

A quarter turn, a lap and a quarter, and a quarter turn back

startOffset turns the ring; it never shortens it. Every badge here carries the same phrase all the way round — count the separators. It is a rotation on the text and not SVG's startOffset along the path, which with a textLength of the whole circumference would have pushed the tail past the path's end, and glyphs that fall off a path are not rendered at all. The value wraps rather than clamping, so 125 is the same badge as 25 and -25 is the same badge as 75.

fluid
04

A 96px chip and a 280px seal, same component

The radius is derived from the box and the text's own height, so there is no radius prop to disagree with size — the two only agree until somebody changes one of them, and the symptom is text clipped at the top of its own badge. The repeat count is derived too, from an estimated glyph advance; the fit is not, because the textPath carries textLength equal to the circumference, so the ring closes exactly whatever the estimate said.

fluid
v1
05

Once round where it wanted twice, and eight times where it wanted four

repeat is the seam the derivation exposes: pass it when you want the count exact, because working it out properly needs the phrase's measured width, which needs a canvas, a layout effect and a client boundary. Both of these override the estimate and neither breaks — the textPath carries textLength equal to the circumference, so the renderer spreads the slack and the ring closes on itself either way. A wrong count costs tracking, never a gap and never an overlap.

fluid
06

Named, for a badge that says something the page does not

The ring is aria-hidden by default and that is deliberate: SVG text is real text, so a seal repeating its phrase three times would be announced three times in every screen reader and show up three times in find-in-page. Pass label and it becomes one role='img' announced under your name instead. Leave it off — the common case — when the badge is a mark.

fluid
SOC 2 Type II · Audited annually ·
07

A centre slot bigger than the badge, and no ring at all

The box reserves size rather than fixing it, so a centre slot larger than the badge grows it instead of overflowing into whatever is next on the page — the ring on the left is 160px around a 190px card. An empty text with a centre slot is just the centre slot, correctly placed; an empty text with nothing in the middle renders nothing at all rather than an empty circle.

fluid
A card wider than its own badge
08

A still seal is the point of a seal

spin={false} on the left, and on the right the same badge under Reduce Motion — turn it on in your OS and reload, and the two are the same picture. That is also what an install renders with the stylesheet import forgotten: the phrase set all the way around its circle, at the right radius, evenly spaced, legible. The rotation is the flourish, not the mark, which is why that path needed no separate design.

fluid
AutoMindz Solutions
AutoMindz Solutions