10 KiB
Plan: Adding a New RTL Example for Both Radix and Base
This document outlines the step-by-step process for adding a new RTL (Right-to-Left) example component for both the Radix UI and Base UI implementations.
Overview
When adding a new RTL example, you need to:
- Create the example component files (Base and Radix)
- Register the examples in the examples index
- Add documentation sections to the MDX files
- Ensure RTL UI components exist (if needed)
Prerequisites
- The component must have RTL UI components available in:
apps/v4/examples/base/ui-rtl/(for Base UI)apps/v4/examples/radix/ui-rtl/(for Radix UI)
- If RTL UI components don't exist, they need to be created first (outside this plan's scope)
Important: Follow Demo Structure
The RTL example should follow the same structure as the demo version.
For example:
dropdown-menu-rtl.tsxshould be similar todropdown-menu-demo.tsxavatar-rtl.tsxshould be similar toavatar-demo.tsxalert-rtl.tsxshould be similar toalert-demo.tsx
Key Points:
- Use the same component structure and layout as the demo
- Keep the same visual elements (icons, badges, groups, etc.)
- Only add translations for text content that needs to be translated
- If the demo has no text (like
avatar-demo), the RTL example should also have minimal/no text - The main difference is using RTL UI components (
@/examples/{base|radix}/ui-rtl/) and passing thedirprop
Step-by-Step Process
Step 1: Create Base UI RTL Example Component
Location: apps/v4/examples/base/{component-name}-rtl.tsx
Important: Before creating the RTL example, check the corresponding demo file ({component-name}-demo.tsx) and use it as a reference. The RTL example should follow the same structure, layout, and visual elements as the demo, but use RTL UI components and add translations for text content.
Template Structure:
"use client"
import * as React from "react"
import {
// Import component parts from RTL UI
Component,
ComponentPart1,
ComponentPart2,
} from "@/examples/base/ui-rtl/{component-name}"
import {
useTranslation,
type Translations,
} from "@/components/language-selector"
const translations: Translations = {
en: {
dir: "ltr",
values: {
// English translation keys
key1: "English text",
key2: "More English text",
},
},
ar: {
dir: "rtl",
values: {
// Arabic translations
key1: "نص عربي",
key2: "المزيد من النص العربي",
},
},
he: {
dir: "rtl",
values: {
// Hebrew translations
key1: "טקסט עברי",
key2: "עוד טקסט עברי",
},
},
}
// Data structure (if needed)
const items = [
{
value: "item-1",
labelKey: "key1" as const,
},
] as const
export function {ComponentName}Rtl() {
const { dir, t } = useTranslation(translations, "ar")
return (
<Component className="..." dir={dir}>
{/* Component implementation using t[key] for translations */}
</Component>
)
}
Key Points:
- Use
"use client"directive at the top - Import from
@/examples/base/ui-rtl/{component-name} - Use
useTranslationhook (notuseLanguage) - Export function name should be
{ComponentName}Rtl(PascalCase) - Default language is
"ar"(Arabic) - Use
dirfromuseTranslationand pass it to the component - Use
t[key]to access translated values
Step 2: Create Radix UI RTL Example Component
Location: apps/v4/examples/radix/{component-name}-rtl.tsx
Template Structure:
"use client"
import * as React from "react"
import {
// Import component parts from RTL UI
Component,
ComponentPart1,
ComponentPart2,
} from "@/examples/radix/ui-rtl/{component-name}"
import {
useTranslation,
type Translations,
} from "@/components/language-selector"
// Same translations structure as Base UI
const translations: Translations = {
// ... same as Base UI
}
export function {ComponentName}Rtl() {
const { dir, t } = useTranslation(translations, "ar")
return (
<Component
// Radix-specific props (e.g., type="single", collapsible)
className="..."
dir={dir}
>
{/* Component implementation */}
</Component>
)
}
Key Differences from Base UI:
- Import from
@/examples/radix/ui-rtl/{component-name} - May need Radix-specific props (e.g.,
type="single" collapsiblefor Accordion) - Otherwise follows the same pattern as Base UI
Step 3: Register Examples in Examples Index
Location: apps/v4/examples/__index__.tsx
For Radix UI: Find the Radix section (around line 86) and add:
"{component-name}-rtl": {
name: "{component-name}-rtl",
filePath: "examples/radix/{component-name}-rtl.tsx",
component: React.lazy(async () => {
const mod = await import("./radix/{component-name}-rtl")
const exportName =
Object.keys(mod).find(
(key) =>
typeof mod[key] === "function" || typeof mod[key] === "object"
) || "{component-name}-rtl"
return { default: mod.default || mod[exportName] }
}),
},
For Base UI: Find the Base section (around line 5015) and add the same structure, but with:
filePath: "examples/base/{component-name}-rtl.tsx"import("./base/{component-name}-rtl")
Important:
- Place entries alphabetically within their respective sections
- Ensure the export name matches the function name in your component file
- The
namefield should match the file name (without extension)
Step 4: Add Documentation to Base UI MDX
Location: apps/v4/content/docs/components/base/{component-name}.mdx
Add a new RTL section before the "API Reference" section:
## RTL
To enable RTL support in shadcn/ui, see the [RTL configuration guide](/docs/rtl).
<ComponentPreview
styleName="base-nova"
name="{component-name}-rtl"
direction="rtl"
previewClassName="h-96"
/>
Key Points:
- Use
styleName="base-nova"for Base UI - Use
name="{component-name}-rtl"(matches the registered example name) - Use
direction="rtl"to enable RTL mode - Adjust
previewClassNameas needed (e.g.,h-96for accordion)
Step 5: Add Documentation to Radix UI MDX
Location: apps/v4/content/docs/components/radix/{component-name}.mdx
Add the same RTL section, but with:
styleName="radix-nova"instead ofbase-nova- Same
nameanddirectionprops
Step 6: Verify the Implementation
Checklist:
- Both example files exist and export the correct function name
- Examples are registered in
__index__.tsxfor both Radix and Base - Documentation sections added to both MDX files
- Component uses
useTranslationhook correctly - Translations include
en,ar, andhelanguages - Component passes
dirprop to the root component - RTL UI components exist in both
ui-rtldirectories - Language selector appears automatically (handled by
ComponentPreviewTabs)
Step 7: Build Examples Index
Important: After adding new RTL examples, you must run the build command to regenerate the examples index:
pnpm examples:build
This command will:
- Scan all example files
- Regenerate
apps/v4/examples/__index__.tsx - Ensure all examples are properly registered
Note: The examples index is auto-generated, so manual edits to __index__.tsx will be overwritten. Always run the build command after adding new examples.
Example: Accordion RTL
As a reference, here's what was done for the accordion component:
-
Base UI Example:
apps/v4/examples/base/accordion-rtl.tsx- Uses
@/examples/base/ui-rtl/accordion - Exports
AccordionRtl - Uses
defaultValue={["item-1"]}(Base UI pattern) - Follows the same structure as
accordion-demo.tsxbut with translations
- Uses
-
Radix UI Example:
apps/v4/examples/radix/accordion-rtl.tsx- Uses
@/examples/radix/ui-rtl/accordion - Exports
AccordionRtl - Uses
type="single" collapsible defaultValue="item-1"(Radix UI pattern) - Follows the same structure as
accordion-demo.tsxbut with translations
- Uses
-
Registered in:
apps/v4/examples/__index__.tsx- Radix: line ~86
- Base: line ~5015
-
Documentation added to:
apps/v4/content/docs/components/base/accordion.mdxapps/v4/content/docs/components/radix/accordion.mdx
Other Examples:
dropdown-menu-rtl.tsxfollowsdropdown-menu-demo.tsxstructureavatar-rtl.tsxfollowsavatar-demo.tsxstructure (no text labels)alert-rtl.tsxfollowsalert-demo.tsxstructure
Common Patterns
Translation Keys
- Use descriptive, semantic keys (e.g.,
question1,answer1,selectFruit) - Avoid generic keys like
text1,text2 - Group related translations logically
Component Props
- Always pass
dir={dir}to the root component - Use
classNamefor styling (e.g.,max-w-md,w-28) - Follow Base UI vs Radix UI prop differences
Data Structures
- Use
as constfor type safety - Reference translation keys in data structures
- Example:
{ value: "item-1", questionKey: "question1" as const }
Troubleshooting
Language selector not appearing:
- Ensure
direction="rtl"is set inComponentPreview - Check that
ComponentPreviewTabswraps withLanguageProviderwhendirection === "rtl"
Component not found:
- Verify the example is registered in
__index__.tsx - Check that the export name matches the function name
- Ensure the file path is correct
Translations not working:
- Verify
useTranslationhook is used correctly - Check that translation keys match between
translationsobject and usage - Ensure
diris passed to components that need it
RTL UI components missing:
- Check if components exist in
ui-rtldirectories - May need to create RTL versions of UI components first (separate task)
Notes
- The language selector is automatically injected by
ComponentPreviewTabswhendirection="rtl" - No need to manually add
<LanguageSelector>to example components - The
useTranslationhook automatically handles language context fromLanguageProvider - Default language is Arabic (
"ar"), but users can switch via the language selector