Thorium Web has two preference layers:
- Global preferences — app-level settings that apply regardless of which reader is active. Currently owns
locale, from which UI direction is derived automatically. - Reader preferences — reader-specific settings (typography, theming, actions, etc.) passed to
ThPreferencesProviderorThAudioPreferencesProvider.
ThGlobalPreferencesProvider must wrap your entire application, outside of any reader. It provides React Aria's I18nProvider internally so that useLocale() is available everywhere, and keeps document.documentElement.dir in sync with the locale.
import { createGlobalPreferences, ThGlobalPreferencesProvider } from "@edrlab/thorium-web/preferences";
const globalPrefs = createGlobalPreferences({ locale: "ar" });
export default function App() {
return (
<ThStoreProvider>
<ThGlobalPreferencesProvider initialPreferences={ globalPrefs }>
{ children }
</ThGlobalPreferencesProvider>
</ThStoreProvider>
);
}createGlobalPreferences is server-safe — it can be called in a Next.js layout.tsx Server Component without triggering a "use client" boundary error.
Unsupported locales are silently discarded and the UI falls back to browser language detection.
Use StatefulGlobalPreferencesProvider to persist the locale to localStorage via Redux:
import { StatefulGlobalPreferencesProvider } from "@edrlab/thorium-web/components";
<ThStoreProvider>
<StatefulGlobalPreferencesProvider initialPreferences={ globalPrefs }>
{ children }
</StatefulGlobalPreferencesProvider>
</ThStoreProvider>import { useGlobalPreferences } from "@edrlab/thorium-web/preferences";
function LocaleSwitcher() {
const { preferences, updatePreferences } = useGlobalPreferences();
return (
<select
value={ preferences.locale ?? "" }
onChange={ e => updatePreferences({ locale: e.target.value || undefined }) }
>
<option value="">Browser default</option>
<option value="fr">Français</option>
<option value="ar">العربية</option>
</select>
);
}Direction is derived from the locale by React Aria — it is not stored. Use useLocale() anywhere inside ThGlobalPreferencesProvider:
import { useLocale } from "react-aria";
function MyComponent() {
const { direction } = useLocale(); // "ltr" | "rtl"
}Important
useLocale().direction reflects the UI locale direction. For publication reading direction (e.g. an RTL Arabic ebook), use state.publication.isRTL from the Redux store instead.
Implement ThGlobalPreferencesAdapter to plug in your own storage:
import { ThGlobalPreferencesAdapter, ThGlobalPreferences } from "@edrlab/thorium-web/preferences";
class MyGlobalAdapter implements ThGlobalPreferencesAdapter {
getPreferences(): ThGlobalPreferences { /* ... */ }
setPreferences(prefs: ThGlobalPreferences): void { /* ... */ }
subscribe(cb: (prefs: ThGlobalPreferences) => void): void { /* ... */ }
unsubscribe(cb: (prefs: ThGlobalPreferences) => void): void { /* ... */ }
}
<ThGlobalPreferencesProvider adapter={ new MyGlobalAdapter() }>
{ children }
</ThGlobalPreferencesProvider>In case you need to use the Preferences package, you can use the following helpers to create and merge preferences objects.
It also provides a ThPreferencesProvider component that makes the preferences available to all components, as well as a context hook, usePreferences(), that allows you to access and update the preferences.
The createPreferences helper allows you to create a new preferences object with your own custom configuration. This is the primary way to set up the preferences for your Thorium Web implementation.
import { createPreferences, ThSettingsKeys, ThActionsKeys, ThDocumentTitleFormat } from "@edrlab/thorium-web/preferences";
const prefs = createPreferences({
metadata: {
documentTitle: { format: ThDocumentTitleFormat.title }
},
typography: {
optimalLineLength: 55,
minimalLineLength: 40,
maximalLineLength: 70,
pageGutter: 20,
},
settings: {
reflowOrder: [ThSettingsKeys.theme, ThSettingsKeys.textGroup, ThSettingsKeys.layout],
fxlOrder: [ThSettingsKeys.theme],
webPubOrder: [ThSettingsKeys.theme, ThSettingsKeys.textGroup],
// keys, text, spacing…
},
actions: {
reflowOrder: [ThActionsKeys.toc, ThActionsKeys.settings],
fxlOrder: [ThActionsKeys.toc, ThActionsKeys.settings],
webPubOrder: [ThActionsKeys.toc, ThActionsKeys.settings],
// keys, collapse…
},
// theming, affordances, shortcuts, docking…
});Note
locale and direction are not accepted here — use createGlobalPreferences instead. See Global Preferences above.
createPreferences is server-safe and can be called in Server Components.
Let's imagine you need to add a custom action key to the preferences. You can do this by following these steps:
- Define your action keys
- Extend
CustomizableKeys - Create preferences with your custom keys type
import { createPreferences, CustomizableKeys } from "@edrlab/thorium-web/preferences";
import { ThActionsKeys } from "@edrlab/thorium-web/preferences/models/enums";
// 1. Define your action keys
enum MyActions {
customAction = "customAction"
}
// 2. Extend CustomizableKeys
type MyKeys = {
action: MyActions | ThActionsKeys; // Include default actions
} & CustomizableKeys;
// 3. Create preferences
const prefs = createPreferences<MyKeys>({
actions: {
reflowOrder: [ThActionsKeys.settings, MyActions.customAction],
keys: {
[MyActions.customAction]: {
visibility: "always",
shortcut: null
}
}
}
});The ThPreferencesProvider component provides a React context for accessing Thorium Web preferences throughout your application. It serves as the central point for managing and distributing preference settings to all components.
import { ThPreferencesProvider } from "@edrlab/thorium-web/preferences";
function App() {
return (
<ThPreferencesProvider
adapter={ yourAdapter } // Optional: custom adapter for persistence
devMode={ true } // Optional: enable dev mode
initialPreferences={ prefs } // Optional: initial preferences
>
<YourApp />
</ThPreferencesProvider>
);
}adapter?: Optional custom adapter for persisting preferencesdevMode?: Optional boolean to enable dev mode – this will turn every content protection setting tofalseinitialPreferences?: Optional initial preferences object – note this will override the default preferences and the dev modechildren: Your application components
import { usePreferences } from "@edrlab/thorium-web/preferences";
function MyComponent() {
const { preferences } = usePreferences<MyKeys>();
}The updatePreferences function allows you to update preferences values. It expects a complete preferences object.
import { usePreferences } from "@edrlab/thorium-web/preferences";
function MyComponent() {
const { preferences, updatePreferences } = usePreferences<MyKeys>();
const handleUpdate = () => {
updatePreferences({
...preferences,
metadata: { documentTitle: { format: ThDocumentTitleFormat.titleAndAuthor } }
});
};
return (
<button onClick={ handleUpdate }>Update Document Title Format</button>
);
}- The provider should be placed high in your component tree
- Use a custom adapter for persistence if needed
- The
usePreferenceshook is read-only - updates should be handled through your adapter - You can nest multiple providers to override preferences for specific parts of your application
Implement ThPreferencesAdapter to plug in your own storage (e.g. a database, AsyncStorage, or a custom Redux slice):
import { ThPreferencesAdapter, ThPreferences } from "@edrlab/thorium-web/preferences";
class MyAdapter<T extends CustomizableKeys> implements ThPreferencesAdapter<T> {
getPreferences(): ThPreferences<T> { /* ... */ }
setPreferences(prefs: ThPreferences<T>): void { /* ... */ }
subscribe(cb: (prefs: ThPreferences<T>) => void): void { /* ... */ }
unsubscribe(cb: (prefs: ThPreferences<T>) => void): void { /* ... */ }
}// layout.tsx — Server Component
import { createGlobalPreferences } from "@edrlab/thorium-web/preferences";
import { ThStoreProvider } from "@edrlab/thorium-web/lib";
import { ThGlobalPreferencesProvider } from "@edrlab/thorium-web/preferences";
const globalPrefs = createGlobalPreferences({ locale: "fr" });
export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThStoreProvider>
<ThGlobalPreferencesProvider initialPreferences={ globalPrefs }>
{ children }
</ThGlobalPreferencesProvider>
</ThStoreProvider>
</body>
</html>
);
}// reader page — Client Component
import { createPreferences } from "@edrlab/thorium-web/preferences";
import { StatefulReaderWrapper } from "@edrlab/thorium-web/reader";
const readerPrefs = createPreferences({ /* ... */ });
export default function ReaderPage() {
return (
<StatefulReaderWrapper
{ ...publicationProps }
preferences={{ initialPreferences: readerPrefs }}
/>
);
}Note
html[suppressHydrationWarning] is required because ThDirectionSetter writes document.documentElement.dir client-side after hydration. The attribute is not present in the server-rendered HTML.