GodUIGodUI
129Follow on X

Anatomy of the GodUI Textarea

Why the textarea's focus ring snaps instead of fading, and where to get a ring that fades in on the GPU.

01/02Why the ring snaps
tsx
// shadcn"… transition-[color,box-shadow] …focus-visible:ring-[3px] focus-visible:ring-ring/50"// GodUI: same ring, no transition"… focus-visible:border-ringfocus-visible:ring-[3px] focus-visible:ring-ring/50"
01

Why the ring snaps

shadcn draws the focus ring as a 3px box-shadow and transitions it. A shadow can't move to the compositor: every frame of that fade repaints the textarea.

To fade a ring in on the GPU, it needs its own layer whose opacity changes, and on a <div> that layer is a ::before. A <textarea> is a replaced element, so it gets no ::before. Wrapping it in a <div> would move your className and ref off the element you put them on.

So the Textarea keeps shadcn's ring and drops the transition. The left field in the scene snaps the way the Textarea does; the right one is an Input Group, whose ::before ring fades in (slowed down here). Input Group takes a textarea too, through InputGroupTextarea.

02/02Result
02

The result

Tab into the textarea or click it. The border and ring change in the same frame.

What's animated

InteractionKeyframe / mechanismPropertiesEasingDuration
Focus ringsnaps (box-shadow, no transition)nonenonenone
Border, invalid ringsnapnonenonenone

Why it snaps: the ring is a box-shadow on the textarea itself, and box-shadow can only be animated by repainting. A GPU fade needs a separate layer whose opacity changes, and <textarea> is a replaced element, so it can't have a ::before. A wrapper would give it one, but your className and ref would land on the wrong element. For a ring that fades in, use InputGroupTextarea inside Input Group.

The textarea still grows with its content (field-sizing-content); that resize is the browser's layout, not an animation.

Why GPU-only

A box-shadow transition repaints the textarea on every frame of the fade. GodUI's textarea never animates, so focusing it costs one repaint.

Reduced motion

Nothing to reduce: the ring already appears in a single frame.

Replacing shadcn

npx shadcn add @godui/textarea overwrites components/ui/textarea.tsx. Exports, props and the data-slot attribute match shadcn/ui new-york-v4, so existing imports keep working. The only class removed is transition-[color,box-shadow].