Available in Uniwind 1.4.0+
Overview
ScopedTheme lets you apply a theme to a specific subtree without changing the global app theme. This is useful for previews, isolated surfaces (cards, modals, panels), and mixed-theme layouts where multiple themes need to exist on screen at the same time.
The nearest ScopedTheme wins, so nested scopes work naturally.
ScopedTheme is a runtime theme boundary. Components, hooks, and withUniwind wrapped components inside the boundary resolve styles against the scoped theme.
Basic Example
import { ScopedTheme } from 'uniwind'
import { Text, View } from 'react-native'
export function Example() {
return (
<View className="gap-4">
<View className="p-4 rounded-xl bg-base">
<Text className="text-default">Uses the current app theme</Text>
</View>
<ScopedTheme theme="dark">
<View className="p-4 rounded-xl bg-base">
<Text className="text-default">This subtree uses the dark theme</Text>
<View className="mt-2 size-8 rounded bg-primary" />
</View>
</ScopedTheme>
</View>
)
}
Common Patterns
Side-by-Side Theme Previews
import { ScopedTheme } from 'uniwind'
import { Text, View } from 'react-native'
export function ThemePreviewRow() {
return (
<View className="flex-row gap-3">
<ScopedTheme theme="light">
<View className="flex-1 p-4 bg-base rounded-2xl gap-2">
<Text className="text-default font-bold">Light</Text>
<View className="size-8 rounded-lg bg-primary" />
<Text className="text-default/60 text-xs">Scoped to light</Text>
</View>
</ScopedTheme>
<ScopedTheme theme="dark">
<View className="flex-1 p-4 bg-base rounded-2xl gap-2">
<Text className="text-default font-bold">Dark</Text>
<View className="size-8 rounded-lg bg-primary" />
<Text className="text-default/60 text-xs">Scoped to dark</Text>
</View>
</ScopedTheme>
<ScopedTheme theme="coffee">
<View className="flex-1 p-4 bg-base rounded-2xl gap-2">
<Text className="text-default font-bold">Coffee</Text>
<View className="size-8 rounded-lg bg-primary" />
<Text className="text-default/60 text-xs">Scoped to coffee (custom theme)</Text>
</View>
</ScopedTheme>
</View>
)
}
Nested Scoped Themes
import { ScopedTheme } from 'uniwind'
import { Text, View } from 'react-native'
export function NestedScopes() {
return (
<ScopedTheme theme="coffee">
<View className="w-full p-4 bg-base rounded-2xl gap-3">
<Text className="text-default font-bold">Coffee (outer custom theme)</Text>
<ScopedTheme theme="dark">
<View className="w-full p-4 bg-base rounded-xl gap-3">
<Text className="text-default font-bold">Dark (middle)</Text>
<ScopedTheme theme="light">
<View className="w-full p-3 bg-base rounded-lg gap-2">
<Text className="text-default font-bold">Light (inner)</Text>
<View className="size-6 rounded bg-primary" />
</View>
</ScopedTheme>
</View>
</ScopedTheme>
</View>
</ScopedTheme>
)
}
Nested scopes are great for documentation previews and component galleries where you want to compare variants without changing global theme state.
Hooks and withUniwind in a Scoped Theme
Hooks and HOCs resolve against the closest ScopedTheme boundary.
useUniwind() returns the scoped theme name
useResolveClassNames() resolves class names using scoped theme variables
useCSSVariable() reads CSS variables from the scoped theme
withUniwind() mappings use values resolved from the scoped theme
Example
import { Feather } from '@expo/vector-icons'
import { Pressable, Text, View } from 'react-native'
import {
ScopedTheme,
useCSSVariable,
useResolveClassNames,
useUniwind,
withUniwind,
} from 'uniwind'
const StyledFeather = withUniwind(Feather, {
size: {
fromClassName: 'className',
styleProperty: 'width',
},
color: {
fromClassName: 'className',
styleProperty: 'color',
},
})
function UseUniwindExample() {
const { theme } = useUniwind()
return (
<View className="w-full p-4 bg-base rounded-2xl gap-1">
<Text className="text-default font-bold">useUniwind</Text>
<Text className="text-primary">Current theme: {theme}</Text>
</View>
)
}
function UseResolveClassNamesExample() {
const styles = useResolveClassNames('text-primary')
return (
<View className="w-full p-4 bg-base rounded-2xl gap-1">
<Text className="text-default font-bold">useResolveClassNames</Text>
<Text style={styles}>Resolved from 'text-primary'</Text>
</View>
)
}
function UseCSSVariableExample() {
const primaryColor = useCSSVariable('--color-primary')
return (
<View className="w-full p-4 bg-base rounded-2xl gap-1">
<Text className="text-default font-bold">useCSSVariable</Text>
<Text className="text-primary">--color-primary: {String(primaryColor)}</Text>
</View>
)
}
function WithUniwindExample() {
return (
<View className="w-full p-4 bg-base rounded-2xl gap-2">
<Text className="text-default font-bold">withUniwind</Text>
<View className="flex-row gap-3 items-center">
<StyledFeather name="star" className="size-6 text-primary" />
<StyledFeather name="heart" className="size-6 text-primary" />
<StyledFeather name="zap" className="size-6 text-primary" />
<Text className="text-primary">Icons via withUniwind</Text>
</View>
</View>
)
}
function ThemedButton({ title }: { title: string }) {
return (
<Pressable className="px-6 py-3 bg-primary rounded-xl active:scale-[0.97] active:opacity-80">
<Text className="text-white font-semibold text-center">{title}</Text>
</Pressable>
)
}
export function ScopedThemeHooksExample() {
return (
<View className="w-full gap-3">
<ScopedTheme theme="coffee">
<View className="w-full gap-3">
<ThemedButton title="Coffee Button" />
<UseUniwindExample />
<UseResolveClassNamesExample />
<UseCSSVariableExample />
<WithUniwindExample />
</View>
</ScopedTheme>
<View className="w-full gap-3">
<ThemedButton title="Current Theme Button" />
<UseUniwindExample />
<UseResolveClassNamesExample />
<UseCSSVariableExample />
<WithUniwindExample />
</View>
</View>
)
}
API Reference
Component Signature
import { ScopedTheme } from 'uniwind'
<ScopedTheme theme="dark">
{/* themed subtree */}
</ScopedTheme>
Theme name to apply to this subtree. The theme must exist in your configured Uniwind theme definitions (for example, light, dark, or a custom theme like coffee).
React children rendered inside the scoped theme boundary.
Behavior Notes
ScopedTheme affects only its descendants
- It does not change the global app theme
- Nested scopes override parent scopes for their own subtree
- Multiple scopes can render side by side on the same screen