Enter
Selection

WipeToggle

A switch between two labelled modes — an input-method toggle is the canonical case. The fill sweeps on and off behind an arc, the shell gives where the arc lands, and the active glyph takes the emphasis.

Disabled

Usage

<WipeToggle
  size="md"
  intensity="normal"
  spring="bouncy"
  label="Input mode"
  options={[
    { value: "latin", label: "A" },
    { value: "kana", label: "あ" },
  ]}
/>

How the motion works

The control is rendered twice: an outlined skin with the glyphs in the foreground colour, and an identical set inverted underneath a single clip whose radius springs between covering the button and nothing. Because the two are the same markup at the same size in the same grid cell, the clip boundary inverts the glyphs exactly along its arc — a letter caught half inside the fill is light on the covered side and dark on the bare side with no seam through it. One layer with a recolouring background cannot produce that: the text would have to flip at some instant, and there is no instant that is right while the edge is still crossing it. Both the filled skin and its glyphs sit inside one clipped wrapper rather than carrying a clip each, which is what lets the skin be pushed around by the walls while the glyphs stay put and the two still share an exact arc; both rows also carry a transparent ring of the outlined skin's width, or the glyphs sit a ring-width off and the inversion stops lining up. Anchoring the circle at the left edge rather than the centre is what makes it read as a wipe instead of an iris: the visible edge is always a single convex arc sweeping across, and that curvature is what distinguishes the move from a plain horizontal sweep, which against a rounded rect reads as a progress bar. The walls take the same treatment Tabs gives its container, minus the half of it that does not apply — a wipe has one moving edge and one pinned at the anchor, so there is no body to tug open, but there is very much an edge arriving somewhere. The wall being left is shoved immediately, since the fill is already collapsed against it, while the impulse for the far wall is parked until the arc has actually crossed the button and released only on contact; landing anywhere short of a wall would move neither. Walls move by inset rather than scaleX so the corner radii stay circular, and widening the shell pinches it vertically so it reads as one skin under tension. Beyond the impulses, a wall sits at least as far out as the fill has overrun its own endpoint: the clip has nothing left to reveal past full coverage, so a spring that overshoots there would simply lose its ring, and routing the overrun into the shell is what makes that overshoot visible at all. The emphasis swap is a transform, not a font-size change, so it costs no layout — the row is laid out once at full size for both glyphs and the inactive one shrinks inside space it already holds. Animating font-size would reflow the row every frame and walk the button's width around underneath the clip radius that is measured against it.

usePressuseSpringValueusePeakImpulseclip-path circle

Props

PropTypeDefaultDescription
options[WipeOption, WipeOption]Exactly two. The first is the filled state.
valuestringControlled selection. Omit for uncontrolled use.
defaultValuestringInitial uncontrolled selection. Defaults to the first option.
onChange(value: string) => voidFires on every toggle.
size"sm" | "md" | "lg""md"Control height and glyph scale.
springSpringLike"bouncy"Drives the wipe and both walls.
intensity"subtle" | "normal" | "strong""normal"Scales how hard the walls are shoved.
labelstringAccessible name — the glyphs rarely make one.