Advanced Usage
Beyond the basics, use-styled
offers flexibility for more complex scenarios. Let’s explore some advanced techniques.
Overriding Styles (className
and style
)
Section titled “Overriding Styles (className and style)”While the configuration (base
, variants
, etc.) defines the main styles, you’ll often need to apply specific styles or modifiers directly to the component instance. use-styled
intelligently merges className
and style
props passed directly with those defined in the configuration.
className
: Classes passed directly are combined with the configuration classes usingclsx
andtailwind-merge
. This means you can add new classes or even override conflicting Tailwind/NativeWind utilities.style
(React Native): Style objects passed directly are merged (shallow merge) with the configuration styles. Directly defined properties take precedence.
import { useStyled } from 'use-styled';
const Button = useStyled('button', { base: { className: 'px-4 py-2 rounded bg-blue-500 text-white' }, variants: { size: { large: { className: 'px-6 py-3 text-lg' }, }, },});
function MyComponent() { return ( <Button size="large" className="bg-red-500 mt-4" // Overrides bg-blue-500 and adds margin > Large Red Button </Button> );}
In this example, the large
button would have bg-blue-500
, but the className="bg-red-500 mt-4"
passed directly takes priority due to tailwind-merge
, resulting in a red button with top margin.
Styling Third-Party Components
Section titled “Styling Third-Party Components”A major advantage of use-styled
is its ability to wrap and configure components from any library or even your own custom components. It’s not limited to passing className
or style
.
You can use the configuration (base
, variants
, compoundVariants
) to define any prop that the base component (the first argument of useStyled
) accepts. This allows not only styling but also configuring the behavior or content of the third-party component conditionally through variants.
Common Examples (Focusing on Styles):
Although you can pass any prop, the most common cases still involve className
(for web and NativeWind) and style
(for React Native), when the third-party component supports them:
Example (React with a hypothetical DatePicker
component):
import React from 'react';import { useStyled } from 'use-styled';import HypotheticalDatePicker from 'some-ui-library'; // Third-party component
// Styling the DatePickerconst StyledDatePicker = useStyled(HypotheticalDatePicker, { base: { className: 'border rounded shadow-sm p-2', // Specific props of HypotheticalDatePicker can also go here dateFormat: 'dd/MM/yyyy', }, variants: { fullWidth: { true: { className: 'w-full' }, }, },});
function MyForm() { return ( <StyledDatePicker fullWidth={true} /> );}
Example (React Native with TouchableOpacity
from react-native-gesture-handler
):
import React from 'react';import { Text } from 'react-native';import { useStyled } from 'use-styled';import { TouchableOpacity } from 'react-native-gesture-handler'; // Using from RNGH
const GestureButton = useStyled(TouchableOpacity, { base: { className: 'p-3 rounded-md', activeOpacity: 0.7, // Specific prop of TouchableOpacity }, variants: { intent: { primary: { className: 'bg-purple-600' }, secondary: { className: 'bg-gray-400' }, }, }, defaultVariants: { intent: 'primary', },});
const ButtonText = useStyled(Text, { base: { className: 'text-center font-semibold' }, variants: { intent: { primary: { className: 'text-white' }, secondary: { className: 'text-black' }, }, }, defaultVariants: { intent: 'primary', },});
function MyScreen() { return ( <GestureButton intent="primary" onPress={() => console.log('Pressed!')}> <ButtonText intent="primary">Press Me (Gesture Handler)</ButtonText> </GestureButton> );}
Simply pass the library component as the first argument to useStyled
and configure the styles and variants as usual. You can also pass specific props of the third-party component directly in base
or variants
.
Advanced Example (React Native with @hugeicons/react-native
- Passing Arbitrary Props):
This example demonstrates how to use variants to control props that are not className
or style
. Here, we control which icon (icon
) is rendered and its size (className
for styling) based on the icon
and size
variants.
import { HugeiconsIcon } from '@hugeicons/react-native'import { ArrowDown01Icon, ArrowLeft01Icon, ArrowRight01Icon, ArrowUp01Icon, Settings02Icon,} from '@hugeicons-pro/core-stroke-rounded' // Example, use your icons
import { useStyled } from 'use-styled'
export const Icon = useStyled(HugeiconsIcon, { base: { // Specific base props of HugeiconsIcon could go here }, variants: { icon: { // Variant to control which icon to show ArrowUp: { icon: ArrowUp01Icon, // Passing the 'icon' prop }, ArrowDown: { icon: ArrowDown01Icon, }, ArrowLeft: { icon: ArrowLeft01Icon, }, ArrowRight: { icon: ArrowRight01Icon, }, Settings: { icon: Settings02Icon, }, }, size: { // Variant to control size via className sm: { className: 'w-8 h-8', // Applying size styles }, md: { className: 'w-10 h-10', }, lg: { className: 'w-12 h-12', }, }, }, defaultVariants: { size: 'sm', },})
// Usage:function MyComponent() { return ( <> <Icon icon="ArrowUp" size="lg" className="text-blue-500" /> <Icon icon="Settings" size="sm" className="text-gray-700" /> {/* Note that className here overrides/adds to the size variant */} </> )}
This pattern is extremely powerful for creating configurable and abstract components on top of other libraries.
Structuring Complex Components with useSlot
Section titled “Structuring Complex Components with useSlot”While useStyled
focuses on styling a single base component, the useSlot
hook allows creating Compound Components, grouping related subcomponents under a unified API.
This is ideal for components with distinct parts, like a Button
containing a specific Button.Text
.
Example: Creating a Composite Button
-
Define the styled base components:
import React from 'react';import { useStyled, useSlot } from 'use-styled';import { Pressable, Text } from 'react-native'; // Or 'button'/'span' for web// Button root componentconst ButtonRoot = useStyled(Pressable, { /* ...useStyled config... */base: { className: 'px-4 py-2 rounded-md flex-row items-center justify-center' },variants: {intent: {primary: { className: 'bg-blue-600' },secondary: { className: 'bg-gray-200' },},},defaultVariants: { intent: 'primary' },});// Component for the text inside the buttonconst ButtonTextContent = useStyled(Text, { /* ...useStyled config... */base: { className: 'font-medium text-base' },variants: {intent: {primary: { className: 'text-white' },secondary: { className: 'text-gray-800' },}},defaultVariants: { intent: 'primary' },}); -
Create the compound component with
useSlot
:// Creates the Button compound componentexport const Button = useSlot({Root: ButtonRoot, // Component for <Button>Text: ButtonTextContent, // Component for <Button.Text>}); -
Use the compound API:
function MyComponent() {return (<View className="gap-4"><Button intent="primary" onPress={() => alert('Pressed!')}><Button.Text>Primary Button</Button.Text></Button><Button intent="secondary">{/* The 'intent' prop is passed implicitly or can be explicit */}<Button.Text intent="secondary">Secondary Button</Button.Text></Button></View>);}
This approach creates a clear API (Button
and Button.Text
) and encapsulates the styling and composition logic.
➡️ See the dedicated useSlot
page for a more in-depth explanation and other examples.
Integration with Animation
Section titled “Integration with Animation”use-styled
works perfectly with popular animation libraries like Motion
(web) or Reanimated
/ Moti
(React Native). The key is to pass the animated component from the library as the first argument to useStyled
.
Example (React with Motion):
import React from 'react';import { useStyled } from 'use-styled';import * as motion from 'motion/react-client'
// Styling a motion.divconst AnimatedBox = useStyled(motion.div, { base: { className: 'w-24 h-24 rounded-lg', }, variants: { color: { blue: { className: 'bg-blue-500' }, red: { className: 'bg-red-500' }, }, }, defaultVariants: { color: 'blue', },});
function PulsingBox() { return ( <AnimatedBox color="red" // Motion props are passed directly initial={{ scale: 0.8, opacity: 0.5 }} animate={{ scale: 1, opacity: 1 }} transition={{ duration: 0.8, repeat: Infinity, repeatType: "reverse" }} /> );}
Example (React Native with Moti):
import React from 'react';import { useStyled } from 'use-styled';import { MotiView } from 'moti'; // Using MotiView// For Reanimated, you would create an Animated.View and pass it:// import Animated from 'react-native-reanimated';// const AnimatedView = Animated.createAnimatedComponent(View);
const AnimatedPressable = useStyled(MotiView, { // Or AnimatedView base: { className: 'w-40 h-40 rounded-full justify-center items-center', }, variants: { intent: { confirm: { className: 'bg-green-500' }, cancel: { className: 'bg-gray-500' }, }, }, defaultVariants: { intent: 'confirm', },});
function BouncingButton() { return ( <AnimatedPressable intent="confirm" // Moti props are passed directly from={{ scale: 0.9 }} animate={{ scale: 1 }} transition={{ type: 'spring', loop: true }} /> // Add Text or Icon inside if needed );}
Since use-styled
simply forwards unknown props, you can pass all animation props (initial
, animate
, transition
, from
, exit
, etc.) directly to the styled component, and they will work as expected on the underlying motion
or Moti
/ Reanimated
component.