---
title: useRtl - RTL Direction Support for Vue 3
meta:
- name: description
  content: RTL composable for managing text direction. Reactive isRtl boolean, dir attribute management, subtree overrides, and adapter pattern for framework integration.
- name: keywords
  content: useRtl, RTL, right-to-left, direction, composable, Vue 3, accessibility, i18n
features:
  category: Plugin
  label: 'E: useRtl'
  github: /composables/useRtl/
  level: 2
related:
  - /composables/plugins/use-locale
  - /guide/features/accessibility
---

# useRtl

Right-to-left text direction management with reactive state and subtree overrides.

<DocsPageFeatures :frontmatter />

## Installation

Install the RTL plugin in your app's entry point:

```ts main.ts
import { createApp } from 'vue'
import { createRtlPlugin } from '@vuetify/v0'
import App from './App.vue'

const app = createApp(App)

app.use(createRtlPlugin())

app.mount('#app')
```

### With Options

```ts main.ts
app.use(
  createRtlPlugin({
    default: true, // Start in RTL mode
  })
)
```

## Usage

```ts collapse
import { useRtl } from '@vuetify/v0'

const rtl = useRtl()

// Read direction
rtl.isRtl.value // false (LTR by default)

// Toggle direction
rtl.toggle() // Now true (RTL)

// Direct assignment
rtl.isRtl.value = false // Back to LTR
```

## Adapters

Adapters let you swap the underlying `dir` attribute management without changing your application code.

| Adapter | Import | Description |
|---------|--------|-------------|
| `V0RtlAdapter` | `@vuetify/v0` | Sets `dir` on target element (default) |

The default adapter sets the `dir` attribute on the target element (defaults to `document.documentElement`), enabling native browser RTL support including CSS logical properties and the `:dir()` pseudo-class.

```ts
// Custom target element
app.use(createRtlPlugin({ target: '#app' }))

// Disable dir attribute management
app.use(createRtlPlugin({ target: null }))
```

### Custom Adapters

Implement `RtlAdapter` to control how RTL direction is applied to the DOM:

```ts
import type { RtlAdapter } from '@vuetify/v0'

class CustomRtlAdapter extends RtlAdapter {
  setup (app, context, target) {
    // context.isRtl — reactive ref, write to it to change direction
    // context.toggle — flips isRtl
    watchEffect(() => {
      const el = typeof target === 'string' ? document.querySelector(target) : target
      if (el) el.setAttribute('dir', context.isRtl.value ? 'rtl' : 'ltr')
    })
  }
}

app.use(createRtlPlugin({ adapter: new CustomRtlAdapter() }))
```

```ts
abstract class RtlAdapter {
  abstract setup<T extends RtlAdapterSetupContext> (app: App, context: T, target?: string | HTMLElement | null): void
}
```

## Architecture

`useRtl` is a standalone plugin built with `createPluginContext`; its entire state is one reactive boolean (`isRtl`). Applying that direction to the DOM is delegated to an `RtlAdapter` — the default `V0RtlAdapter` writes the `dir` attribute onto the target element (`document.documentElement` unless overridden). It is independent of `useLocale`; Vuetify wires the two together through its own adapter.

```mermaid "useRtl Architecture"
flowchart TD
  Plugin["createPluginContext"]
  UR["useRtl"]:::primary
  Adapter["RtlAdapter"]
  V0["V0RtlAdapter"]
  DOM["dir attribute on target"]

  Plugin --> UR
  UR --> Adapter
  Adapter --> V0
  V0 --> DOM
```

## Reactivity

| Property | Type | Description |
| - | - | - |
| `isRtl` | `Ref<boolean>` | Writable ref — `true` for RTL, `false` for LTR |
| `toggle` | `() => void` | Flips the current direction |

## Examples

::: gn-example
/composables/use-rtl/direction-toggle

### Direction Toggle

A live RTL/LTR switcher that calls `toggle()` from `useRtl()` and binds the resulting direction string to a container's `dir` attribute. Because `isRtl` is a writable ref, the template derives `direction` via `toRef(() => isRtl.value ? 'rtl' : 'ltr')` and uses it both for the `dir` prop and for conditionally rendering Arabic vs. English copy.

The card inside the panel demonstrates how the browser's native bidirectional layout engine handles the flip: the avatar, name, and action buttons all reflow without any CSS overrides because they're positioned with flexbox and the `dir` attribute propagates automatically to descendants. This is the cheapest way to test whether your own components are already direction-aware — no special `rtl:` variants or logical properties needed if the flex container gets the right `dir`.

Reach for `useRtl()` when you need to read or imperatively change the active direction from script. For locale-linked RTL (e.g. Arabic or Hebrew auto-sets `isRtl`), wire a watcher between `useLocale().selectedId` and `isRtl` inside a custom adapter. For subtree-scoped direction that is isolated from the app-level flag, see `createRtlContext` in the Subtree Overrides section above.

:::

## Recipes

### Standalone Usage

Use `createRtl` to create a raw RTL context without the plugin system — useful for testing or embedding in other composables:

```ts
import { createRtl } from '@vuetify/v0'

const rtl = createRtl({ default: true }) // starts in RTL
rtl.isRtl.value  // true
rtl.toggle()
rtl.isRtl.value  // false
```

`createRtl` accepts `default?: boolean` (initial direction) and returns `{ isRtl: ShallowRef<boolean>, toggle: () => void }`. No `app` is required.

### Styling

The `dir` attribute set by the adapter enables three approaches to direction-aware styling with utility classes:

#### Logical Properties (preferred)

CSS logical properties automatically flip based on `dir`. Use these by default:

```html
<!-- Physical (breaks in RTL) -->
<div class="ml-4 pr-2 left-0 border-l-2">...</div>

<!-- Logical (works in both directions) -->
<div class="ms-4 pe-2 start-0 border-s-2">...</div>
```

| Physical | Logical | CSS Property |
| - | - | - |
| `ml-*` / `mr-*` | `ms-*` / `me-*` | `margin-inline-start` / `end` |
| `pl-*` / `pr-*` | `ps-*` / `pe-*` | `padding-inline-start` / `end` |
| `left-*` / `right-*` | `start-*` / `end-*` | `inset-inline-start` / `end` |
| `border-l-*` / `border-r-*` | `border-s-*` / `border-e-*` | `border-inline-start` / `end` |
| `rounded-l-*` / `rounded-r-*` | `rounded-s-*` / `rounded-e-*` | `border-start-*-radius` / `end` |
| `text-left` / `text-right` | `text-start` / `text-end` | `text-align` |

> [!TIP]
> The utility class names above use UnoCSS `presetWind4` / Tailwind v4 syntax. Exact class names may vary depending on your CSS framework or preset — the underlying CSS logical properties are the same.

#### Direction Variants

For cases logical properties can't handle (like `translate-x`), use the bare class as the LTR default and the `rtl:` variant as the override:

```html
<!-- Mobile drawer: slides from start edge -->
<nav class="-translate-x-full rtl:translate-x-full md:translate-x-0">
  ...
</nav>
```

> [!TIP]
> Avoid the `ltr:` variant — it only applies when an ancestor has an explicit `dir="ltr"` attribute, not as the default. Use the bare utility class for LTR behavior and `rtl:` for the RTL override.

#### Symmetric Shorthand

When both sides use the same value, use `inset-x-*` instead of `left-* right-*`:

```html
<!-- Before -->
<div class="fixed left-0 right-0 top-0">...</div>

<!-- After -->
<div class="fixed inset-x-0 top-0">...</div>
```

### Subtree Overrides

Use `createRtlContext` to scope direction to a subtree — isolated from the app-level direction:

```ts
import { createRtlContext } from '@vuetify/v0'

export const [useLocalRtl, provideLocalRtl, localRtl] =
  createRtlContext({ default: true })
```

```vue collapse no-filename ParentComponent
<script setup lang="ts">
  import { provideLocalRtl } from './rtl-context'

  // Provide to all descendants
  provideLocalRtl()
</script>

<template>
  <slot />
</template>
```

```vue collapse no-filename ChildComponent
<script setup lang="ts">
  import { useLocalRtl } from './rtl-context'

  const rtl = useLocalRtl()
  // rtl.isRtl.value  → true (isolated from app-level direction)
  // rtl.toggle()     → scoped toggle, doesn't affect the rest of the app
</script>
```

> [!TIP]
> Direction is independent from locale. To connect them (e.g., Arabic → RTL), use a custom adapter that watches `useLocale().selectedId` and sets `isRtl` based on a language→direction mapping.

## FAQ

::: faq

??? Does switching the locale automatically change the direction?

No — direction is independent from locale. To link them (e.g. Arabic → RTL), use a custom adapter that watches [useLocale](/composables/plugins/use-locale)'s `selectedId` and sets `isRtl` from a language-to-direction mapping.

??? How do I apply RTL to just one subtree?

Use `createRtlContext` to provide a scoped direction via provide/inject. A descendant reading it is isolated from the app-level flag, so toggling it doesn't affect the rest of the app.

??? Why should I avoid the `ltr:` variant for left-to-right styles?

The `ltr:` variant only applies when an ancestor carries an explicit `dir="ltr"` attribute, not as the default. Use the bare utility class for LTR behavior and `rtl:` for the override — e.g. `-translate-x-full rtl:translate-x-full`.

??? Do I need `rtl:` variants on every margin and padding?

No — prefer CSS logical properties (`ms`/`me`, `ps`/`pe`, `start`/`end`), which flip automatically with the `dir` attribute. Reserve direction variants for the cases logical properties can't express, like `translate-x`.

:::

<DocsApi />
