Skip to content

Advanced Usage

Beyond the basics, use-styled offers flexibility for more complex scenarios. Let’s explore some advanced techniques.

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 using clsx and tailwind-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.

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 DatePicker
const 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

  1. 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 component
    const 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 button
    const 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' },
    });
  2. Create the compound component with useSlot:

    // Creates the Button compound component
    export const Button = useSlot({
    Root: ButtonRoot, // Component for <Button>
    Text: ButtonTextContent, // Component for <Button.Text>
    });
  3. 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.

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.div
const 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.