Skip to content

Shimmer

Draw attention to an element with accent light that travels along its border.

import { Shimmer, ShimmerOptions } from '@youj/motion'

Installation & usage

npm i @youj/motion

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Use it for a single invitation: an AI prompt, a search field, an upgrade link. It keeps moving, so it pulls the eye, which is the point and also the cost. Use one per screen, and not on anything the reader has to concentrate on nearby, like a form or a long text. For the ready-made composer and button looks, use Shimmer Border.

Shimmer wraps anything in the effect and brings no look of its own. Give it a radius and a background.

import { Shimmer } from '@youj/motion'

export function AskField() {
  return (
    <Shimmer className="rounded-xl bg-surface">
      <label className="relative z-2 flex h-12 items-center gap-3 px-4">
        <span className="sr-only">Search the docs or ask a question</span>
        <input placeholder="Search the docs or ask a question…" />
      </label>
    </Shimmer>
  )
}

The element gets a 1px border in --color-line, and the light travels along it. Keep your content above the light's layers with relative z-2.

Reference

Propsidle, intensify, speed… (5)

Props

Shimmer

Shimmer props
PropType
childrenReactNode–
idle'subtle' | 'visible' | 'hidden'

How visible the light is at rest. 'hidden' only shows it on hover / focus.

default 'subtle'

'subtle'
intensifyboolean

Lengthen the comet and add a bloom on hover and keyboard focus.

default true

true
speed'slow' | 'base' | 'fast'

One lap of the border.

default 'base'

'base'
borderWidthnumber

Border width in px.

default 1

1
Other props pass through: extends HTMLAttributes<HTMLDivElement>.

useShimmer

function useShimmer<T extends HTMLElement>(options?: ShimmerOptions): {
  rootProps: { ref; className; style; 'data-intensify'; 'data-reduced' }
  layers: ReactNode
}

ShimmerOptions are the same four options as the component's props:

OptionTypeDefaultDescription
idle'subtle' | 'visible' | 'hidden''subtle'How visible the light is at rest. hidden shows it only on hover and focus.
intensifybooleantrueLengthen the light and add a bloom on hover and keyboard focus.
speed'slow' | 'base' | 'fast''base'How long one lap of the border takes.
borderWidthnumber1Border width in pixels.
MotionLap, intensify · reduced motion

Motion

Motion tokens
MomentTokens
Lapspeed: slow, base, fast
ease.linear

cubic-bezier(0, 0, 1, 1) · deliberate × 10, × 6, × 3.5

cubic-bezier(0, 0, 1, 1)deliberate × 10, × 6, × 3.5
Intensifylonger light, bloom, full strength
duration.slowease.standard

340ms · cubic-bezier(0.2, 0, 0, 1)

340mscubic-bezier(0.2, 0, 0, 1)

Reduced motion

The light is parked at a fixed point on the border and doesn't travel. Hover and focus still intensify it, instantly.

At normal speed a lap takes 5.6 s (slow), 3.36 s (base) or 1.96 s (fast). The loop runs entirely in CSS: the light is a conic gradient whose start angle is a registered @property, so the browser animates it with no JavaScript per frame. The length of the light is a registered property too, so leaving mid-way through intensifying reverses from where it is. The loop pauses while the element is offscreen or the tab is hidden.

Reduced motion

The light stops traveling and stays parked at a fixed point on the border, so the element keeps its accent glint without movement. Hover and focus still lengthen it, instantly, with no transition.

AccessibilityKeyboard, ARIA, focus

Accessibility

  • The light's layers are aria-hidden and ignore the pointer. The effect adds no roles and no tab stops.
  • Keyboard focus intensifies the light exactly like hover, so keyboard users see the same emphasis. It isn't a focus ring, though: keep the control's own focus indicator.
  • A constantly moving element is a distraction for some readers. Keep it to one per screen, and prefer idle="hidden" where the emphasis only matters on interaction.