diff --git a/src/design-system/providers/ThemeProvider.test.tsx b/src/design-system/providers/ThemeProvider.test.tsx
new file mode 100644
index 0000000..4b6726c
--- /dev/null
+++ b/src/design-system/providers/ThemeProvider.test.tsx
@@ -0,0 +1,72 @@
+import { describe, it, expect, beforeEach } from 'vitest'
+import { render, screen } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { ThemeProvider, useTheme } from './ThemeProvider'
+
+function TestConsumer() {
+ const { theme, toggleTheme } = useTheme()
+ return (
+
+ {theme}
+
+
+ )
+}
+
+describe('ThemeProvider', () => {
+ beforeEach(() => {
+ localStorage.clear()
+ document.documentElement.removeAttribute('data-theme')
+ })
+
+ it('defaults to light theme', () => {
+ render(
+
+
+ ,
+ )
+ expect(screen.getByTestId('theme').textContent).toBe('light')
+ })
+
+ it('sets data-theme attribute on document', () => {
+ render(
+
+
+ ,
+ )
+ expect(document.documentElement.dataset.theme).toBe('light')
+ })
+
+ it('toggles theme on button click', async () => {
+ const user = userEvent.setup()
+ render(
+
+
+ ,
+ )
+ await user.click(screen.getByText('Toggle'))
+ expect(screen.getByTestId('theme').textContent).toBe('dark')
+ expect(document.documentElement.dataset.theme).toBe('dark')
+ })
+
+ it('persists theme to localStorage', async () => {
+ const user = userEvent.setup()
+ render(
+
+
+ ,
+ )
+ await user.click(screen.getByText('Toggle'))
+ expect(localStorage.getItem('cameleer-theme')).toBe('dark')
+ })
+
+ it('reads initial theme from localStorage', () => {
+ localStorage.setItem('cameleer-theme', 'dark')
+ render(
+
+
+ ,
+ )
+ expect(screen.getByTestId('theme').textContent).toBe('dark')
+ })
+})
diff --git a/src/design-system/providers/ThemeProvider.tsx b/src/design-system/providers/ThemeProvider.tsx
new file mode 100644
index 0000000..3abe824
--- /dev/null
+++ b/src/design-system/providers/ThemeProvider.tsx
@@ -0,0 +1,57 @@
+import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from 'react'
+
+type Theme = 'light' | 'dark'
+
+interface ThemeContextValue {
+ theme: Theme
+ toggleTheme: () => void
+ setTheme: (theme: Theme) => void
+}
+
+const ThemeContext = createContext(null)
+
+const STORAGE_KEY = 'cameleer-theme'
+
+function getInitialTheme(): Theme {
+ try {
+ const stored = localStorage.getItem(STORAGE_KEY)
+ if (stored === 'dark' || stored === 'light') return stored
+ } catch {
+ // localStorage unavailable
+ }
+ return 'light'
+}
+
+export function ThemeProvider({ children }: { children: ReactNode }) {
+ const [theme, setThemeState] = useState(getInitialTheme)
+
+ const setTheme = useCallback((t: Theme) => {
+ setThemeState(t)
+ document.documentElement.dataset.theme = t
+ try {
+ localStorage.setItem(STORAGE_KEY, t)
+ } catch {
+ // localStorage unavailable
+ }
+ }, [])
+
+ const toggleTheme = useCallback(() => {
+ setTheme(theme === 'light' ? 'dark' : 'light')
+ }, [theme, setTheme])
+
+ useEffect(() => {
+ document.documentElement.dataset.theme = theme
+ }, [theme])
+
+ return (
+
+ {children}
+
+ )
+}
+
+export function useTheme(): ThemeContextValue {
+ const ctx = useContext(ThemeContext)
+ if (!ctx) throw new Error('useTheme must be used within ThemeProvider')
+ return ctx
+}
diff --git a/src/main.tsx b/src/main.tsx
index 8e0deb1..83eded7 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,13 +1,16 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
+import { ThemeProvider } from './design-system/providers/ThemeProvider'
import App from './App'
import './index.css'
createRoot(document.getElementById('root')!).render(
-
+
+
+
,
)