Skip to content

BidiText

XS, under 350B gzipped

Isolates user-controlled text so a mixed-direction sentence does not reorder around it.

Category
Internationalisation
Budget
xs, static display primitive
Runtime deps
None

Preview

Preview language

Reply from محمد (3 replies)

This is the exact fixture the conformance suite renders in CI. Switch the language to see the component mirror and reformat.

Why this exists

BidiText

العربية

A right-to-left name next to a number pulls the number to the wrong side of it. Here the naive version renders the reply count before the name instead of after. The sentence reads backwards, and only for users with RTL names.

What most apps ship

إيان - 42 replies

<span>{name} - {n} replies</span>

BidiText

إيان - 42 replies

bidi-text

Install

npx shadcn@latest add https://kata-ui-rho.vercel.app/r/bidi-text.json

Copies the source into your project.

Source

"use client";

export interface BidiTextProps {
  /** Text of unknown or user-controlled direction. */
  children: React.ReactNode;
  /**
   * "auto" lets the browser infer direction from the first strong character —
   * the right default for user-generated content whose language you do not
   * know at render time.
   */
  dir?: "auto" | "ltr" | "rtl";
}

/**
 * Isolates text whose direction you do not control.
 *
 * When an Arabic or Hebrew name is interpolated into an English sentence, the
 * Unicode bidirectional algorithm reorders the *surrounding* punctuation too:
 * "محمد (3 replies)" can render with the parenthesis and count on the wrong
 * side, and the sentence reads as scrambled to everyone. The bug is invisible
 * in development in an English locale, and reported constantly by users whose
 * names trigger it.
 *
 * `<bdi>` — bidirectional isolate — scopes the reordering to the untrusted run
 * so neighbouring text is unaffected. It is one element, has no styling, and
 * is the entire fix.
 *
 * Wrap any interpolated value you did not author: names, titles, filenames,
 * search terms, message bodies.
 */
export function BidiText({ children, dir = "auto" }: BidiTextProps) {
  return <bdi dir={dir}>{children}</bdi>;
}

What CI checks

← All components