topiray-auth-react
v1.2.0
Published
A customizable React authentication component library
Maintainers
Readme
Topiray Auth React Component Library
A comprehensive, themeable React component library for authentication flows. Built with TypeScript and CSS Modules, designed for customization, and tested against WCAG 2.2 Level AA — see Accessibility.
Features
- 🎨 Fully Themeable - Customize colors, fonts, spacing, and more
- 🔧 CSS Modules - Strongly-typed, scoped styling
- 📱 Responsive Design - Mobile-first approach with breakpoint support
- ♿ Accessible - ARIA labels, keyboard navigation, focus management
- 🔐 Complete Auth Flow - Sign in/up, 2FA, email verification, password reset
- ⚡ TypeScript - Full type safety and IntelliSense support
- 🎯 Tree Shakeable - Import only what you need
- 🌐 i18n Support - Built-in internationalization with customizable translations
Installation
npm install topiray-auth-react
# or
yarn add topiray-auth-reactPeer Dependencies
npm install react react-dom lucide-react @mui/materialCSS Import
Important: You must import the CSS file for proper styling:
// Import the CSS file in your main App component or index file
import 'topiray-auth-react/dist/topiray-auth-react.css'Or if you're using a bundler that supports CSS imports:
/* In your main CSS file */
@import 'topiray-auth-react/dist/topiray-auth-react.css';Quick Start
import React from 'react'
import {
ThemeProvider,
defaultTheme,
TwoPanelLayout,
AuthCard,
SignInForm
} from 'topiray-auth-react'
// Import the CSS file
import 'topiray-auth-react/dist/topiray-auth-react.css'
function App() {
const handleSignIn = (email: string, password: string) => {
console.log('Sign in:', { email, password })
}
return (
<ThemeProvider theme={defaultTheme}>
<TwoPanelLayout
rightContent={
<AuthCard>
<SignInForm
onSubmit={handleSignIn}
onForgotPassword={() => console.log('Forgot password')}
onSignUp={() => console.log('Sign up')}
onSocialLogin={(provider) => console.log('Social:', provider)}
authProviders={['google', 'apple', 'facebook']}
/>
</AuthCard>
}
/>
</ThemeProvider>
)
}Components
Auth Components
AuthCard
Container wrapper for auth forms with consistent styling.
import { AuthCard } from 'topiray-auth-react'
<AuthCard>
{/* Your auth form content */}
</AuthCard>SignInForm
Complete sign-in form with email/password and customizable social login options.
import { SignInForm } from 'topiray-auth-react'
<SignInForm
onSubmit={(email, password) => handleSignIn(email, password)}
onForgotPassword={() => navigate('/forgot-password')}
onSignUp={() => navigate('/sign-up')}
onSocialLogin={(provider) => handleSocialLogin(provider)}
authProviders={['google', 'apple', 'facebook']}
isLoading={isSigningIn}
logoSrc="/your-logo.png"
/>SignUpForm
Account creation form with business/individual selection and customizable social login.
import { SignUpForm } from 'topiray-auth-react'
<SignUpForm
onSubmit={(email, password, accountType) => handleSignUp(email, password, accountType)}
onSignIn={() => navigate('/sign-in')}
onSocialLogin={(provider) => handleSocialLogin(provider)}
authProviders={['google', 'apple', 'facebook']}
isLoading={isSigningUp}
logoSrc="/your-logo.png"
/>ForgottenPasswordForm
Password reset request form.
import { ForgottenPasswordForm } from 'topiray-auth-react'
<ForgottenPasswordForm
onSubmit={(email) => handlePasswordReset(email)}
isLoading={isLoading}
/>VerifyEmailForm
Email verification flow with resend functionality.
import { VerifyEmailForm } from 'topiray-auth-react'
<VerifyEmailForm
onSubmit={() => navigate('/sign-in')}
onResendEmail={() => handleResendEmail()}
email="[email protected]"
isLoading={isLoading}
/>Two-Factor Authentication
TwoFactorSetupForm
QR code setup for authenticator apps.
import { TwoFactorSetupForm } from 'topiray-auth-react'
<TwoFactorSetupForm
onNext={() => navigate('/2fa/verify')}
onCancel={() => navigate('/dashboard')}
qrCodeUri="otpauth://totp/App:[email protected]?secret=SECRET&issuer=App"
sharedKey="JBSWY3DPEHPK3PXP"
isLoading={isLoading}
/>TwoFactorSetupEnterVerificationForm
6-digit code entry with auto-focus and paste support.
import { TwoFactorSetupEnterVerificationForm } from 'topiray-auth-react'
<TwoFactorSetupEnterVerificationForm
onVerify={(code) => handleVerification(code)}
isLoading={isVerifying}
error={error}
backRoute="/2fa/setup"
/>TwoFactorSetupCompleteForm
Backup codes display with copy/download functionality.
import { TwoFactorSetupCompleteForm } from 'topiray-auth-react'
<TwoFactorSetupCompleteForm
onDone={() => navigate('/dashboard')}
backupCodes={recoveryCodes}
isLoading={isLoading}
/>Layout Components
TwoPanelLayout
Responsive layout with left branding panel and right content area.
import { TwoPanelLayout } from 'topiray-auth-react'
<TwoPanelLayout
leftContent={<YourBrandingContent />}
rightContent={<YourAuthForm />}
leftBackgroundImage="/background.jpg"
logoSrc="/logo.png"
/>NavLinksLayout
Flexible content layout for pages with navigation.
import { NavLinksLayout } from 'topiray-auth-react'
<NavLinksLayout
middle={<MainContent />}
right={<SidebarContent />}
/>Common Components
Button
Versatile button with loading states and variants.
import { Button } from 'topiray-auth-react'
<Button variant="primary" isLoading={isLoading} fullWidth>
Sign In
</Button>
<Button variant="secondary" icon="google">
Sign in with Google
</Button>AlertMessage
Dismissible alert messages for different states.
import { AlertMessage } from 'topiray-auth-react'
<AlertMessage
type="error"
message="Invalid credentials"
dismissible
onDismiss={() => setError(null)}
/>SocialLoginButtons
Pre-configured social login buttons.
import { SocialLoginButtons } from 'topiray-auth-react'
<SocialLoginButtons
onSocialLogin={(provider) => handleSocialLogin(provider)}
providers={['google', 'apple', 'facebook']}
orientation="vertical"
isLoading={isLoading}
/>Customizing Social Login Providers
You can customize which social login providers are displayed using the authProviders prop on both SignInForm and SignUpForm:
// Show only Google and Apple
<SignInForm
authProviders={['google', 'apple']}
onSocialLogin={(provider) => handleSocialLogin(provider)}
// ... other props
/>
// Show all available providers (default)
<SignUpForm
authProviders={['apple', 'google', 'facebook']}
onSocialLogin={(provider) => handleSocialLogin(provider)}
// ... other props
/>
// Show only Google
<SignInForm
authProviders={['google']}
onSocialLogin={(provider) => handleSocialLogin(provider)}
// ... other props
/>
// Hide social login entirely by setting empty array or omitting the prop
<SignInForm
authProviders={[]}
// ... other props (onSocialLogin not needed if no providers)
/>Available providers:
'apple'- Apple Sign In'google'- Google Sign In'facebook'- Facebook Login
Default behavior: If authProviders is not specified, it defaults to ['apple', 'google', 'facebook'].
Real-World Usage Examples
Complete Sign-In Page
import React, { useState, useEffect } from 'react'
import { useNavigate, useLocation } from 'react-router-dom'
import {
ThemeProvider,
defaultTheme,
TwoPanelLayout,
AuthCard,
SignInForm,
AlertMessage
} from 'topiray-auth-react'
// Import the CSS file
import 'topiray-auth-react/dist/topiray-auth-react.css'
function SignInPage() {
const navigate = useNavigate()
const location = useLocation()
const [error, setError] = useState<string | null>(null)
const [isLoading, setIsLoading] = useState(false)
// Get success message from registration
const successMessage = location.state?.message
const handleSignIn = async (email: string, password: string) => {
try {
setError(null)
setIsLoading(true)
const result = await signInAPI(email, password)
if (result.succeeded) {
navigate('/dashboard')
} else if (result.requiresTwoFactor) {
navigate('/2fa/signin', { state: { userId: result.userId } })
} else if (result.isNotAllowed) {
navigate(`/verify-email?email=${encodeURIComponent(email)}`)
} else {
setError('Invalid email or password.')
}
} catch (error) {
setError('An error occurred during sign in.')
} finally {
setIsLoading(false)
}
}
const handleSocialLogin = async (provider: 'google' | 'apple' | 'facebook') => {
try {
setError(null)
setIsLoading(true)
// Initiate social login flow
await socialSignInAPI(provider)
} catch (error) {
setError(`Failed to sign in with ${provider}`)
} finally {
setIsLoading(false)
}
}
return (
<ThemeProvider theme={defaultTheme}>
<TwoPanelLayout
rightContent={
<AuthCard>
{successMessage && (
<AlertMessage message={successMessage} type="success" />
)}
{error && (
<AlertMessage message={error} type="error" />
)}
<SignInForm
onSubmit={handleSignIn}
onForgotPassword={() => navigate('/forgot-password')}
onSignUp={() => navigate('/sign-up')}
onSocialLogin={handleSocialLogin}
authProviders={['google', 'apple']} // Only show Google and Apple
isLoading={isLoading}
/>
</AuthCard>
}
/>
</ThemeProvider>
)
}Two-Factor Setup with QR Code
import React, { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import QRCode from 'qrcode'
import {
TwoPanelLayout,
AuthCard,
TwoFactorSetupForm,
AlertMessage
} from 'topiray-auth-react'
function TwoFactorSetupPage() {
const navigate = useNavigate()
const [qrCodeDataUrl, setQrCodeDataUrl] = useState<string | null>(null)
const [sharedKey, setSharedKey] = useState<string | null>(null)
const [error, setError] = useState<string | null>(null)
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
generateQRCode()
}, [])
const generateQRCode = async () => {
try {
setIsLoading(true)
const result = await createAuthenticatorKeyAPI()
if (result.authenticatorUri && result.sharedKey) {
const qrDataUrl = await QRCode.toDataURL(result.authenticatorUri, {
width: 200,
margin: 2
})
setQrCodeDataUrl(qrDataUrl)
setSharedKey(result.sharedKey)
// Render QR code
setTimeout(() => {
const container = document.getElementById('qr-code-container')
if (container && qrDataUrl) {
container.innerHTML = `<img src="${qrDataUrl}" alt="QR Code" />`
}
}, 100)
} else {
setError('Failed to generate QR code.')
}
} catch (error) {
setError('Failed to generate QR code.')
} finally {
setIsLoading(false)
}
}
return (
<TwoPanelLayout
rightContent={
<AuthCard>
{error && <AlertMessage message={error} type="error" />}
<TwoFactorSetupForm
onNext={() => navigate('/2fa/verify')}
onCancel={() => navigate('/dashboard')}
qrCodeUri={qrCodeDataUrl}
sharedKey={sharedKey}
isLoading={isLoading}
/>
</AuthCard>
}
/>
)
}Theming
Using Built-in Themes
import { ThemeProvider, defaultTheme, darkTheme } from 'topiray-auth-react'
// Light theme
<ThemeProvider theme={defaultTheme}>
<App />
</ThemeProvider>
// Dark theme
<ThemeProvider theme={darkTheme}>
<App />
</ThemeProvider>Creating Custom Themes
import { createCustomTheme } from 'topiray-auth-react'
const corporateTheme = createCustomTheme({
colors: {
primary: '#0066cc',
secondary: '#6b7280',
tertiary: "#f3f4f6",
background: '#ffffff',
surface: '#f9fafb',
text: '#111827',
textSecondary: '#6b7280',
border: '#d1d5db',
success: '#10b981',
warning: '#f59e0b',
error: '#ef4444',
info: '#3b82f6',
hover: '#f3f4f6',
active: '#e5e7eb',
disabled: '#9ca3af',
inputBackground: '#ffffff',
inputBorder: '#d1d5db',
inputText: '#111827',
inputPlaceholder: '#9ca3af',
buttonPrimary: '#0066cc',
buttonPrimaryText: '#ffffff',
buttonSecondary: '#6b7280',
buttonSecondaryText: '#ffffff',
buttonSocial: '#374151',
buttonSocialText: '#ffffff'
},
brand: {
logo: '/corporate-logo.png',
logoAlt: 'Corporate Logo'
},
customization: {
showFormHeader: true,
showSocialLogin: true,
showBackArrow: true,
showLogo: true,
roundedCorners: true,
animations: true
}
})
<ThemeProvider theme={corporateTheme}>
<App />
</ThemeProvider>Advanced Theme Customization
const advancedTheme = createCustomTheme({
colors: {
// Custom color palette
primary: '#7c3aed',
secondary: '#64748b',
// ... other colors
},
components: {
spacing: {
xs: '0.25rem',
sm: '0.5rem',
md: '1rem',
lg: '1.5rem',
xl: '2rem'
},
borderRadius: {
sm: '0.25rem',
md: '0.5rem',
lg: '0.75rem',
xl: '1rem'
},
typography: {
fontFamily: '"Inter", system-ui, sans-serif',
fontSize: {
xs: '0.75rem',
sm: '0.875rem',
md: '1rem',
lg: '1.125rem',
xl: '1.25rem',
xxl: '1.5rem'
}
}
},
customization: {
backgroundImage: '/custom-background.jpg',
leftPanelContent: <CustomBrandingComponent />
}
})CSS Custom Properties
The library uses CSS custom properties for theming. Make sure to import the CSS file first, then you can override these properties:
// First, import the CSS file
import 'topiray-auth-react/dist/topiray-auth-react.css'/* Then override custom properties in your CSS */
:root {
--topiray-color-primary: #your-brand-color;
--topiray-color-surface: #your-surface-color;
--topiray-font-family: 'Your Font', sans-serif;
--topiray-radius-md: 12px;
--topiray-spacing-lg: 24px;
}Properties with a contrast threshold
These carry an accessibility requirement, so if you override one, measure it. Each is
also settable through the theme object (focusRing, link, errorSurface,
errorText, and so on for success, warning and info).
| Property | Requirement |
|---|---|
| --topiray-color-focus-ring | 3:1 against the page background — it is the only thing showing where the keyboard is (1.4.11) |
| --topiray-color-link | 4.5:1 against the background behind it (1.4.3) |
| --topiray-color-input-border | 3:1 against the surface behind the field, or the field's own fill must supply that contrast (1.4.11) |
| --topiray-button-primary and --topiray-button-primary-text | 4.5:1 against each other (1.4.3) |
| --topiray-color-error-surface / -error-text | 4.5:1 against each other. Likewise success, warning, info |
Two of these are worth spelling out, because both were wrong in this library until they were measured:
- A tint of an accent colour is not a background you can put that accent colour on. The alerts used a 10% alpha tint of each state colour with the state colour as text, which measured between 1.91:1 and 3.16:1. Alpha also meant the true ratio depended on whatever was painted behind the alert, so it could not be relied on at all.
- A focus ring flush against a control has no guaranteed contrast with it. The ring
is drawn with
outline-offset, so it sits on the page background — a blue ring directly on a blue button is a 1:1 ratio however carefully the colour was picked.
Integration Patterns
With React Router
import { BrowserRouter, Routes, Route } from 'react-router-dom'
// Import the CSS file once at the app level
import 'topiray-auth-react/dist/topiray-auth-react.css'
function App() {
return (
<ThemeProvider theme={defaultTheme}>
<BrowserRouter>
<Routes>
<Route path="/signin" element={<SignInPage />} />
<Route path="/signup" element={<SignUpPage />} />
<Route path="/forgot-password" element={<ForgotPasswordPage />} />
<Route path="/verify-email" element={<VerifyEmailPage />} />
<Route path="/2fa/setup" element={<TwoFactorSetupPage />} />
<Route path="/2fa/verify" element={<TwoFactorVerifyPage />} />
<Route path="/2fa/complete" element={<TwoFactorCompletePage />} />
</Routes>
</BrowserRouter>
</ThemeProvider>
)
}With State Management
// Using React Context
const AuthContext = createContext()
function AuthProvider({ children }) {
const [user, setUser] = useState(null)
const [isLoading, setIsLoading] = useState(false)
const login = async (email, password) => {
setIsLoading(true)
try {
const result = await signInAPI(email, password)
setUser(result.user)
return result
} finally {
setIsLoading(false)
}
}
return (
<AuthContext.Provider value={{ user, isLoading, login }}>
{children}
</AuthContext.Provider>
)
}With Form Validation
import { z } from 'zod'
const signInSchema = z.object({
email: z.string().email('Invalid email address'),
password: z.string().min(8, 'Password must be at least 8 characters')
})
function SignInPage() {
const [errors, setErrors] = useState({})
const handleSignIn = (email, password) => {
try {
signInSchema.parse({ email, password })
setErrors({})
// Proceed with sign in
} catch (error) {
setErrors(error.flatten().fieldErrors)
}
}
return (
<SignInForm
onSubmit={handleSignIn}
authProviders={['google', 'apple']}
// Pass validation errors to form
/>
)
}TypeScript Support
Full TypeScript support with comprehensive type definitions:
import type {
ThemeConfig,
SignInFormProps,
AuthCardProps
} from 'topiray-auth-react'
// Custom theme with full type safety
const myTheme: ThemeConfig = {
colors: {
primary: '#0066cc',
// ... TypeScript will validate all required properties
},
// ... rest of theme configuration
}
// Component props are fully typed
const handleSignIn: SignInFormProps['onSubmit'] = (email, password) => {
// email and password are properly typed as strings
}
// Social login providers are strictly typed
const handleSocialLogin = (provider: 'google' | 'apple' | 'facebook') => {
// provider parameter is type-safe
}Accessibility
The library targets WCAG 2.2 Level AA, which is the standard UK public sector bodies are held to by the Public Sector Bodies (Websites and Mobile Applications) (No. 2) Accessibility Regulations 2018.
What is checked, and how
Conformance is measured rather than asserted. npm test runs a suite against a real
browser — every component, in both the light and dark themes, at desktop width and at
320 CSS px:
| Check | Covers |
|---|---|
| axe-core via @axe-core/playwright, tags wcag2a, wcag2aa, wcag21a, wcag21aa, wcag22aa | 1.1.1, 1.3.1, 1.4.3, 3.3.2, 4.1.2 and everything else a rule engine can decide |
| Focus visibility | 2.4.7 — each control is photographed focused and unfocused and the two must differ |
| Target size | 2.5.8 — 24x24 CSS px, implementing the criterion's inline and spacing exceptions |
| Non-text contrast | 1.4.11 — control boundaries at 3:1, which axe has no rule for |
| Reflow | 1.4.10 — no horizontal scrolling at 320 CSS px |
| Semantics | Labels that survive typing, autocomplete tokens, live regions, heading levels, radio group behaviour |
The last row exists because a rule engine cannot see the failures that matter most here. axe raised no objection to the credential fields when they were labelled by placeholder alone: a placeholder does contribute an accessible name, so the rule passes — and the name still disappears the moment somebody types. A clean axe run is evidence, not a conformance claim.
Areas that remain unverified are listed honestly: no screen reader pass (NVDA, JAWS, VoiceOver) has been carried out, and no testing has been done with disabled users. Automated testing finds somewhere between a third and a half of real accessibility problems and none of the ones requiring judgement.
What the components do for you
- Visible, persistent labels on every credential field, associated by
for/id. Not placeholders (3.3.2, 2.5.3). - Autofill tokens —
email,current-password,new-password,one-time-code— so a password manager can recognise the field (1.3.5, 3.3.8). - Headings for form titles, at a level you choose with
headingLevel, defaulting toh1(1.3.1, 2.4.6). - A visible focus indicator on every control: a 3px outline offset from the control so it lands on the page background rather than on the control's own fill (2.4.7, 1.4.11).
- Colours measured against their own surfaces, including the four alert styles and the primary button (1.4.3).
- Live regions for status messages, so "Copied!" reaches somebody who is not looking at the button (4.1.3).
- A submit button that keeps focus while it is working — marked
aria-disabledandaria-busyrather than natively disabled, which would drop the keyboard user back to the top of the document at the moment they pressed it. - Reduced motion honoured — every transition and hover translation is switched off
under
prefers-reduced-motion: reduce. - Translated accessible names. Every name assistive technology reads comes from the dictionary, so a Welsh page does not announce its buttons in English.
Choosing a heading level
Each form renders its title as an h1 by default, which is right when the form is the
page. If your application already renders an h1 around it, say so:
<SignInForm headingLevel="h2" onSubmit={handleSignIn} />Running the suite
npm test # the whole suite, headless
npm run test:a11y:ui # the Playwright UI, for working through a failure
npm run a11y # the harness on its own, at http://127.0.0.1:5177/a11y.htmlA failure stops a release: the accessibility job in the publish workflow is a blocking gate, not an advisory one.
Internationalization (i18n)
All auth components support internationalization out of the box. English is the default language. You can provide custom translations for any language by wrapping your app with the I18nProvider.
Default Behavior (English)
Without any i18n configuration, all components render in English. No setup is required.
Adding a New Language
- Create a translation JSON file following the same structure as the built-in English translations. All keys live under the
topiray.authnamespace:
// fr.json
{
"topiray": {
"auth": {
"signIn": {
"title": "Se connecter",
"emailPlaceholder": "E-mail",
"passwordPlaceholder": "Mot de passe",
"submitButton": "Continuer",
"submittingButton": "Connexion en cours...",
"dividerText": "ou se connecter avec",
"forgotPassword": "Mot de passe oublié ?",
"noAccount": "Vous n'avez pas de compte ?",
"signUpLink": "S'inscrire"
},
"signUp": {
"title": "Créez votre compte",
"emailPlaceholder": "E-mail",
"passwordPlaceholder": "Mot de passe",
"submitButton": "Créer un compte",
"submittingButton": "Création du compte...",
"dividerText": "ou s'inscrire avec",
"hasAccount": "Vous avez déjà un compte ?",
"signInLink": "Se connecter"
},
"forgottenPassword": {
"title": "Mot de passe oublié ?",
"description": "Entrez l'adresse e-mail associée à votre compte et nous vous enverrons un lien unique pour réinitialiser votre mot de passe.",
"emailPlaceholder": "E-mail",
"submitButton": "Réinitialiser le mot de passe",
"submittingButton": "Envoi en cours..."
},
"resetPassword": {
"title": "Réinitialiser votre mot de passe",
"description": "Entrez votre nouveau mot de passe ci-dessous. Assurez-vous qu'il est sécurisé et comporte au moins 8 caractères.",
"newPasswordPlaceholder": "Nouveau mot de passe",
"confirmPasswordPlaceholder": "Confirmer le mot de passe",
"submitButton": "Réinitialiser le mot de passe",
"submittingButton": "Réinitialisation...",
"errors": {
"passwordRequired": "Le mot de passe est requis",
"confirmPasswordRequired": "La confirmation du mot de passe est requise",
"passwordsMismatch": "Les mots de passe ne correspondent pas"
}
},
"verifyEmail": {
"title": "Vérifiez votre e-mail",
"imageAlt": "Vérification de l'e-mail",
"descriptionPrefix": "Nous avons envoyé un e-mail de vérification à",
"descriptionSuffix": ". Veuillez cliquer sur le lien dans cet e-mail pour continuer.",
"emailFallback": "[votre e-mail]",
"checkInbox": "Vérifier ma boîte de réception",
"resendEmail": "Renvoyer l'e-mail",
"resendingEmail": "Envoi en cours..."
},
"twoFactor": {
"setup": {
"title": "Authentification multi-facteurs",
"description": "Utilisez votre application d'authentification pour scanner ce code QR. Si vous n'avez pas d'application d'authentification sur votre appareil, vous devrez en installer une maintenant.",
"learnMore": "En savoir plus",
"cantScanQr": "Impossible de scanner le code QR ?",
"qrPlaceholder": "Le code QR apparaîtra ici",
"manualEntryKey": "Clé de saisie manuelle :",
"cancelButton": "Annuler",
"nextButton": "Suivant",
"loadingButton": "Chargement..."
},
"verify": {
"title": "Entrez votre code de vérification",
"description": "Entrez le code que vous voyez dans votre application d'authentification",
"verifyButton": "Vérifier",
"verifyingButton": "Vérification...",
"helpText": "Vous pouvez aussi coller votre code à 6 chiffres"
},
"complete": {
"title": "Vous êtes prêt",
"descriptionLine1": "Vous pouvez maintenant utiliser l'application d'authentification mobile pour obtenir un code d'authentification chaque fois que vous vous connectez.",
"descriptionLine2": "Enregistrez ces codes de secours à usage unique dans un endroit sûr.",
"copyButton": "Copier",
"copiedButton": "Copié !",
"downloadButton": "Télécharger",
"footerText": "Ces codes de secours vous permettent de vous connecter si vous ne pouvez pas recevoir de SMS ou si vous n'avez pas accès à vos autres méthodes d'authentification à deux facteurs.",
"doneButton": "Terminé",
"loadingButton": "Chargement...",
"copyCodesButton": "Copier les codes",
"copiedCodesButton": "Copié !"
}
}
}
}
}- Wrap your app with the
I18nProviderand pass your translations:
import { ThemeProvider, I18nProvider, defaultTheme } from 'topiray-auth-react'
import fr from './i18n/fr.json'
function App() {
return (
<ThemeProvider theme={defaultTheme}>
<I18nProvider translations={fr}>
{/* Your auth components */}
</I18nProvider>
</ThemeProvider>
)
}Partial Translations
You only need to provide the keys you want to override. Any missing keys will automatically fall back to the default English translations:
import { I18nProvider } from 'topiray-auth-react'
const customTranslations = {
topiray: {
auth: {
signIn: {
title: "Welcome back",
submitButton: "Log in"
}
}
}
}
<I18nProvider translations={customTranslations}>
{/* SignInForm will show "Welcome back" and "Log in",
all other strings fall back to English */}
</I18nProvider>Dynamic Language Switching
Switch languages at runtime by updating the translations passed to I18nProvider:
import { I18nProvider, en } from 'topiray-auth-react'
import fr from './i18n/fr.json'
import de from './i18n/de.json'
const languages = { en, fr, de }
function App() {
const [locale, setLocale] = useState('en')
return (
<I18nProvider translations={languages[locale]}>
<select onChange={(e) => setLocale(e.target.value)} value={locale}>
<option value="en">English</option>
<option value="fr">Français</option>
<option value="de">Deutsch</option>
</select>
{/* Your auth components */}
</I18nProvider>
)
}Translation Keys Reference
All translation keys are nested under topiray.auth. Here is the full key structure:
| Namespace | Keys |
|-----------|------|
| topiray.auth.signIn | title, emailPlaceholder, passwordPlaceholder, submitButton, submittingButton, dividerText, forgotPassword, noAccount, signUpLink |
| topiray.auth.signUp | title, emailPlaceholder, passwordPlaceholder, submitButton, submittingButton, dividerText, hasAccount, signInLink |
| topiray.auth.forgottenPassword | title, description, emailPlaceholder, submitButton, submittingButton |
| topiray.auth.resetPassword | title, description, newPasswordPlaceholder, confirmPasswordPlaceholder, submitButton, submittingButton, errors.passwordRequired, errors.confirmPasswordRequired, errors.passwordsMismatch |
| topiray.auth.verifyEmail | title, imageAlt, descriptionPrefix, descriptionSuffix, emailFallback, checkInbox, resendEmail, resendingEmail |
| topiray.auth.twoFactor.setup | title, description, learnMore, cantScanQr, qrPlaceholder, manualEntryKey, cancelButton, nextButton, loadingButton |
| topiray.auth.twoFactor.verify | title, description, verifyButton, verifyingButton, helpText |
| topiray.auth.twoFactor.complete | title, descriptionLine1, descriptionLine2, copyButton, copiedButton, downloadButton, footerText, doneButton, loadingButton, copyCodesButton, copiedCodesButton |
Browser Support
- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+
Authors
JayArrowz
Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Submit a pull request
Support
For questions and support:
- Create an issue on GitHub
- Check the documentation
- Review the demo application
Built with ❤️ for the React community
