Skip to content
Sign in
ConversationTranslate toggleMenu

Translate togglePro

A message in two languages that never jumps: switching crossfades in place, and the block is always as tall as the longer version.

ProA team thread in Portuguese, German and English. Each message switches language in place, with no jump.

Installation

$ pnpm dlx shadcn@latest add @chatcn-pro/translate-toggle

Usage

tsx
import { TranslateToggle } from "@/components/ui/chatcn/pro/translate-toggle"

<TranslateToggle
  original={text}
  translation={translated}
  from={{ code: "pt", name: "Portuguese" }}
  to={{ code: "en", name: "English" }}
  onTranslate={translate}
/>

Both versions sit in the same grid cell, so the message takes the height of the longer one and switching moves nothing below it. The swap crossfades with a 2 px blur at the midpoint so it reads as one text changing. The line under the text says Translated from Portuguese with Show original, or the language with Translate to English; the button holds both labels in one cell, so its width never changes. With no translation yet, the first press calls onTranslate and shows Translating to English; the original stays readable meanwhile. Each version carries its lang, so screen readers switch voice.

Examples

States

ProTranslated, showing the original, not translated yet, translating, could not translate, disabled, and a long message.

Props

PropTypeNotes
original, translationReactNodetranslation is missing until translated
from, to{ code, name }BCP 47 code and the language name
view, defaultView, onViewChange"original" | "translation"
status"idle" | "translating" | "done" | "error"Inferred when omitted
onTranslate() => voidAsked for when there is no translation yet
disabledboolean
labelsPartial<TranslateToggleLabels>Every string, for translation

Keyboard

KeyAction
Enter or SpaceSwitch language

Accessibility

  • The toggle has aria-pressed for the translation; the hidden version is aria-hidden.
  • Status changes are a polite live region; a failure is an alert.

Motion

200 ms crossfade on opacity and filter. Reduced motion keeps the fade without the blur.