Conty’s theming system is built on CSS custom properties and seamlessly integrates with next-themes for React applications. It supports light and dark modes out of the box.
Theme Architecture
Conty uses a two-layer theming approach:
Semantic Tokens - JavaScript/TypeScript values exported from @conty/tokens
CSS Custom Properties - Runtime theme variables that change based on the active theme
Define semantic tokens
Core color values are defined in TypeScript: // packages/tokens/src/index.ts
export const semanticTokens = {
color: {
surface: {
default: "oklch(1 0 0)" ,
defaultDark: "#0b0d10"
}
}
}
Map to CSS variables
Tokens are mapped to CSS custom properties in theme.css: :root {
--background : oklch ( 1 0 0 );
}
.dark {
--background : #0b0d10 ;
}
Reference in components
Components use CSS variables that automatically update: < div className = "bg-background text-foreground" >
Themed content
</ div >
Light and Dark Themes
Conty provides complete light and dark color schemes:
Light Theme (Default)
/* From packages/tokens/src/theme.css */
:root {
--background : oklch ( 1 0 0 ); /* Pure white */
--foreground : oklch ( 0.2178 0 0 ); /* Near black */
--primary : oklch ( 0.6248 0.2042 257.0818 ); /* Brand blue */
--primary-foreground : oklch ( 1 0 0 ); /* White on brand */
--muted : oklch ( 0.96 0 0 ); /* Light gray surface */
--muted-foreground : oklch ( 0.5103 0 0 ); /* Medium gray text */
--border : oklch ( 0.92 0 0 ); /* Light border */
--destructive : oklch ( 0.6496 0.2362 26.9032 ); /* Error red */
/* ... more tokens */
}
Dark Theme
.dark {
--background : #0b0d10 ; /* Deep dark blue */
--foreground : oklch ( 0.9551 0 0 ); /* Near white */
--primary : oklch ( 0.6248 0.2042 257.0818 ); /* Same brand blue */
--primary-foreground : oklch ( 0.9551 0 0 ); /* Near white on brand */
--muted : oklch ( 0.252 0 0 ); /* Dark gray surface */
--muted-foreground : oklch ( 0.683 0 0 ); /* Light gray text */
--border : oklch ( 0.28 0 0 ); /* Dark border */
--destructive : oklch ( 0.6496 0.2362 26.9032 ); /* Same error red */
/* ... more tokens */
}
Notice that the brand primary color stays the same in both themes. Its OKLCH lightness (62.48%) is carefully calibrated to work on both light and dark backgrounds.
Complete CSS Theme Definition
/* From packages/tokens/src/theme.css */
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--radius-sm: var(--radius-sm);
--radius-md: var(--radius-md);
--radius-lg: var(--radius-lg);
}
:root {
--background : oklch ( 1 0 0 );
--foreground : oklch ( 0.2178 0 0 );
--card : oklch ( 1 0 0 );
--card-foreground : oklch ( 0.2178 0 0 );
--popover : oklch ( 1 0 0 );
--popover-foreground : oklch ( 0.2178 0 0 );
--primary : oklch ( 0.6248 0.2042 257.0818 );
--primary-foreground : oklch ( 1 0 0 );
--secondary : oklch ( 0.96 0 0 );
--secondary-foreground : oklch ( 0.2178 0 0 );
--muted : oklch ( 0.96 0 0 );
--muted-foreground : oklch ( 0.5103 0 0 );
--accent : oklch ( 0.96 0 0 );
--accent-foreground : oklch ( 0.2178 0 0 );
--destructive : oklch ( 0.6496 0.2362 26.9032 );
--destructive-foreground : oklch ( 1 0 0 );
--border : oklch ( 0.92 0 0 );
--input : oklch ( 0.96 0 0 );
--ring : oklch ( 0.8452 0 0 );
--radius-sm : 0.525 rem ;
--radius-md : 0.625 rem ;
--radius-lg : 0.725 rem ;
}
.dark {
--background : #0b0d10 ;
--foreground : oklch ( 0.9551 0 0 );
--card : oklch ( 0.2 0 0 );
--card-foreground : oklch ( 0.9551 0 0 );
--popover : oklch ( 0.16 0 0 );
--popover-foreground : oklch ( 0.9551 0 0 );
--primary : oklch ( 0.6248 0.2042 257.0818 );
--primary-foreground : oklch ( 0.9551 0 0 );
--secondary : oklch ( 0.2178 0 0 );
--secondary-foreground : oklch ( 0.9551 0 0 );
--muted : oklch ( 0.252 0 0 );
--muted-foreground : oklch ( 0.683 0 0 );
--accent : oklch ( 0.2178 0 0 );
--accent-foreground : oklch ( 0.9551 0 0 );
--destructive : oklch ( 0.6496 0.2362 26.9032 );
--destructive-foreground : oklch ( 1 0 0 );
--border : oklch ( 0.28 0 0 );
--input : oklch ( 0.2178 0 0 );
--ring : oklch ( 0.5103 0 0 );
}
Setting Up Theme Switching
Conty integrates seamlessly with next-themes for React applications:
Next.js App Router
Next.js Pages Router
Vite/React
// app/providers.tsx
'use client'
import { ThemeProvider } from 'next-themes'
export function Providers ({ children } : { children : React . ReactNode }) {
return (
< ThemeProvider
attribute = "class"
defaultTheme = "system"
enableSystem
disableTransitionOnChange
>
{ children }
</ ThemeProvider >
)
}
// app/layout.tsx
import { Providers } from './providers'
import '@conty/tokens/theme.css'
export default function RootLayout ({ children }) {
return (
< html lang = "en" suppressHydrationWarning >
< body >
< Providers > { children } </ Providers >
</ body >
</ html >
)
}
// pages/_app.tsx
import { ThemeProvider } from 'next-themes'
import '@conty/tokens/theme.css'
import type { AppProps } from 'next/app'
export default function App ({ Component , pageProps } : AppProps ) {
return (
< ThemeProvider
attribute = "class"
defaultTheme = "system"
enableSystem
>
< Component { ... pageProps } />
</ ThemeProvider >
)
}
// main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { ThemeProvider } from 'next-themes'
import '@conty/tokens/theme.css'
import App from './App'
ReactDOM . createRoot ( document . getElementById ( 'root' ) ! ). render (
< React.StrictMode >
< ThemeProvider
attribute = "class"
defaultTheme = "system"
enableSystem
>
< App />
</ ThemeProvider >
</ React.StrictMode >
)
Creating a Theme Toggle
Build a theme switcher component:
'use client'
import { useTheme } from 'next-themes'
import { useEffect , useState } from 'react'
import { Button } from '@conty/ui'
export function ThemeToggle () {
const [ mounted , setMounted ] = useState ( false )
const { theme , setTheme } = useTheme ()
// Avoid hydration mismatch
useEffect (() => setMounted ( true ), [])
if ( ! mounted ) return null
return (
< Button
variant = "outline"
size = "icon"
onClick = { () => setTheme ( theme === 'dark' ? 'light' : 'dark' ) }
>
{ theme === 'dark' ? '🌞' : '🌙' }
</ Button >
)
}
Custom Themes
Create custom color schemes by extending the base theme:
Custom Brand Color
/* custom-theme.css */
:root {
/* Override brand primary */
--primary : oklch ( 0.65 0.25 145 ); /* Custom green */
}
.dark {
--primary : oklch ( 0.65 0.25 145 ); /* Same in dark mode */
}
Multiple Theme Variants
/* Ocean theme */
[ data-theme = "ocean" ] {
--primary : oklch ( 0.6 0.15 230 ); /* Deep blue */
--background : oklch ( 0.98 0.01 230 ); /* Blue-tinted white */
--muted : oklch ( 0.94 0.02 230 ); /* Blue-gray */
}
/* Forest theme */
[ data-theme = "forest" ] {
--primary : oklch ( 0.55 0.18 150 ); /* Forest green */
--background : oklch ( 0.98 0.01 150 ); /* Green-tinted white */
--muted : oklch ( 0.94 0.02 150 ); /* Green-gray */
}
Then update your ThemeProvider:
< ThemeProvider
attribute = "data-theme"
defaultTheme = "light"
themes = { [ 'light' , 'dark' , 'ocean' , 'forest' ] }
>
{ children }
</ ThemeProvider >
Programmatic Theme Access
Access theme values in JavaScript:
Using CSS Variables
Using Semantic Tokens
function MyComponent () {
// Read CSS variable value
const primaryColor = getComputedStyle ( document . documentElement )
. getPropertyValue ( '--primary' )
. trim ()
return (
< div style = { { backgroundColor: primaryColor } } >
Themed background
</ div >
)
}
Prefer using Tailwind classes or CSS variables over programmatic access. This allows the browser to handle theme switching more efficiently.
Respecting System Preferences
Conty automatically respects the user’s system theme preference:
< ThemeProvider
attribute = "class"
defaultTheme = "system" // Matches OS preference
enableSystem // Listen for OS changes
>
{ children }
</ ThemeProvider >
You can also detect the system preference manually:
function useSystemTheme () {
const [ isDark , setIsDark ] = useState (
() => window . matchMedia ( '(prefers-color-scheme: dark)' ). matches
)
useEffect (() => {
const mediaQuery = window . matchMedia ( '(prefers-color-scheme: dark)' )
const handler = ( e : MediaQueryListEvent ) => setIsDark ( e . matches )
mediaQuery . addEventListener ( 'change' , handler )
return () => mediaQuery . removeEventListener ( 'change' , handler )
}, [])
return isDark ? 'dark' : 'light'
}
Theme-Aware Components
Create components that adapt to the active theme:
import { useTheme } from 'next-themes'
function Logo () {
const { theme } = useTheme ()
return (
< img
src = { theme === 'dark' ? '/logo-dark.svg' : '/logo-light.svg' }
alt = "Logo"
/>
)
}
CSS Custom Property Reference
All CSS Custom Properties
Main application background
Card/container background
Popover/dropdown background
Text on primary backgrounds
Text on secondary surfaces
Text on accent backgrounds
Text on destructive backgrounds
Best Practices
Every component should be tested in both light and dark mode to ensure proper contrast and legibility. // Use theme toggle in development
< ThemeToggle />
Reference semantic color names (background, foreground, primary) instead of specific color values: // Good
< div className = "bg-background text-foreground" />
// Avoid
< div style = { { backgroundColor: 'white' , color: 'black' } } />
Avoid theme-specific logic
Let CSS handle theme switching instead of conditional rendering: // Good - CSS handles the theme
< div className = "bg-card border" />
// Avoid - unnecessary JavaScript
const { theme } = useTheme ()
< div className = { theme === 'dark' ? 'bg-gray-900' : 'bg-white' } />
Prevent flash of unstyled content
Use suppressHydrationWarning on the html element and avoid rendering theme-dependent content on the server: < html suppressHydrationWarning >
Next Steps
Color Tokens Explore all available color tokens and their values
Components See how tokens are used in Conty components