shadcn.io is not affiliated with official shadcn/ui
Language Switch Dialog Block for Radix UI
A docs site's settings page with its language dialog shown open: eight languages named in their own script, each with a translation coverage bar, and an Apply button that re-reads the landing page behind it, flipping it right-to-left for Arabic.
Installation
Overview
Let readers pick the language of a site and see it change, with this language dialog for React with Radix UI. It opens from the Site language page of Babelpage, a multilingual docs site: the header holds an outline Button with a globe and the name of the language the page reads in now, and the landing page is drawn under a soft scrim inside a hairline frame with a plus mark on each corner. The dialog is a Radio Group of eight languages, each named in its own script with the English name beside it, a thin Progress bar and "92% translated" so a reader knows what they will get. Apply, the only Shine Button, closes the dialog and the page behind it changes: its headline, sentence and button are typed in all eight languages, and Arabic turns the whole page right-to-left. For preview the dialog is drawn open in place when the page loads; the header button opens the real Dialog, with the same list and the same choice. For docs sites, help centres, storefronts and any app with a language menu.
FAQ
Why is the dialog already open, and what do I keep?
So you can see it without a click. On load the dialog is a copy drawn in place over the page: a native dialog element that isn't modal, so nothing is trapped, hidden or focused, and the page around the preview keeps working. The header button opens the real Dialog, modal, with its focus trap, Escape and focus return, and the copy stays closed until a reload. Both show the same LanguageFields component and share the block's state, so a language chosen in the copy is chosen in the real one. In your app keep the Dialog and drop the copy: the "in-place" state, the scrim and the dialog element inside the frame.
What changes when I press Apply?
The landing page drawn behind the dialog. Its headline, sentence and button are typed in all eight languages in LANGUAGES, the language chip in its top bar and the header button show the new name, and a polite status message says what changed. Arabic also sets dir="rtl" on the drawn page, so the rows mirror and the text reads from the right; every other language sets its own lang. Cancel, Escape and the close button leave the page as it was and reset the choice.
What stays local, and how do I wire it for real?
Everything. The translations, the coverage percentages and the choice live in the file and in the block's state; no locale is set on your browser, no cookie is written and no request goes out. In your app send the chosen id to your router or i18n library in apply (a locale prefix on the URL, or a stored preference), and compute the percentages from your catalogue of strings: translated divided by total, per language. Swap the typed headline for your own message files.
How is coverage shown, and what does it mean?
Each row prints "74% translated" and draws the same share as a thin bar. The numbers are typed here as the share of the site's strings that have a translation; a language below 100% shows the rest in the original. The bar is decoration and hidden from assistive technology, because the percentage is already in the row's text.
Is it accessible from the keyboard?
Yes. The real dialog is named by its title and described by its sentence, focus is trapped while it is open, and Escape, Cancel and the close button return focus to the header button. The languages are a radio group: the arrow keys move and choose in one step, Tab leaves the group, and each name carries its own lang so a screen reader pronounces it in that language. The trigger is the button wrapped in DialogTrigger with asChild, and the dialog is controlled with open and onOpenChange. The copy shown on load moves no focus; Escape inside it, Cancel and its close button close it and put focus on the header button.
How does it look on a phone?
The copy shown on load docks to the foot of the page as a bottom sheet with Apply pinned at the bottom and the eight languages scrolling above it. The real dialog keeps the Dialog's own phone layout: a sheet stuck to the bottom of the screen. The drawn page keeps its headline, sentence and button and drops the menu bars.