Skip to content
TopClass UI

Provider

One provider at the root of your app, with direction and locale as props. It is required, and everything it does is listed on this page.

Where it goes

Your root layout, wrapping everything. direction and locale come from the route, never from the DOM.

app/[locale]/layout.tsx
import { TopClassProvider } from 'topclassui/provider'
import { ThemeScript } from 'topclassui/theme'

export default async function RootLayout({ children, params }: {
  children: React.ReactNode
  params: Promise<{ locale: string }>
}) {
  const { locale } = await params
  const dir = locale === 'ar' ? 'rtl' : 'ltr'
  return (
    <html lang={locale} dir={dir} data-theme="dark" suppressHydrationWarning>
      <head><ThemeScript /></head>
      <body>
        <TopClassProvider direction={dir} locale={locale}>
          {children}
        </TopClassProvider>
      </body>
    </html>
  )
}

The props

Prop, Type, Default, What it does
PropTypeDefaultWhat it does
childrenReactNode-Your app.
classNamestring-Classes for the one div the provider renders, the isolation root.
direction'ltr' | 'rtl'requiredThe text direction, from the route. Popups, gestures and placement follow it.
localestringrequiredPicks the built-in labels and the Intl formatters.
labelsProviderLabels-Override any built-in string; the rest keep their defaults.
numberingSystem'latn' | 'arab'-Digits in counts and dates. Left out, the locale decides, and Latin digits otherwise.
adaptive{ query?: string | undefined }{ query: '(width < 48rem)' }The width below which adaptive components switch to their mobile layout.
collectionPartial<CollectionDefaults>{ visibleRows: 7, virtualizeAbove: 200, loadMoreThreshold: 10, estimateRowHeight: 36, overscan: 6 }Defaults for every list: visible rows, windowing and page-by-page loading.
toastToastSettingsInput{ placement: 'bottom-end', limit: 3, duration: 5000, tone: 'neutral' }Where toasts appear, how many show at once and for how long. Read by the outermost provider only.

Why direction is a prop

Reading document.dir in an effect would give the server one answer and the browser another for one frame, which shows as a flash in the wrong direction. A prop from the layout has the same value in both places.

Wrong
useEffect(() => {
  setDir(document.documentElement.dir)
}, [])
Right
<html dir={dir}>
  <TopClassProvider direction={dir}>

What it renders

Direction provider

Popups read the direction from here, so a Select opens on the correct side in Arabic.

Toast viewport

One live region, positioned by direction, with the safe-area padding.

Tooltip provider

A shared delay, so the second tooltip opens at once.

Isolation root

A div with isolation: isolate, so overlays stack above your page without z-index conflicts.

ThemeScript

A small inline script, first in head, reads the stored preference and sets data-theme before the first paint. Without it the page renders in the default mode and then switches, which shows a flash of the wrong mode. The script is a constant string, so a strict Content Security Policy can allow it by the hash below.

import { ThemeScript } from 'topclassui/theme'

<head><ThemeScript /></head>
Content Security Policy
Content-Security-Policy: script-src 'self' 'sha256-DXE/BJ+WKtAgobNXKiOEuUkho9yY2f/ky8nwg8sLL/8='

In a single-page app

In a single-page app nothing React renders runs before the first paint, so the same script goes in index.html above the module script: paste the THEME_SCRIPT string from topclassui/theme into a script tag. The initial lang, dir and data-theme are static attributes on the html element. When the visitor changes locale, update the html attributes and the provider props at the same time.

index.html
<!doctype html>
<html lang="en" dir="ltr" data-theme="dark">
  <head>
    <meta charset="UTF-8" />
    <script>(function(){try{var m=["light","dark","ocean"];var t=null;try{t=localStorage.getItem('tc-theme')}catch(e){}if(!t||t==='system'||m.indexOf(t)<0){t=matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'}document.documentElement.dataset.theme=t}catch(e){}})()</script>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>
NextYour first component