Styling

className on every component, when to reach for style instead, and how to build your own components on top.

Every component in PanelUI takes a className, and it is the intended way to change how one looks. Classes are Tailwind's, compiled for React Native by Uniwind — so px-4, flex-row, rounded-xl and bg-primary all mean what you expect.

<Button className="w-full rounded-full">Continue</Button>
<Card className="border-info/40 bg-info-soft">…</Card>

className merges, it does not replace

What you pass is merged with the component's own classes through tailwind-merge, so a class of yours wins over the one it conflicts with and leaves everything else alone.

// Keeps the height, padding, radius, border and press behaviour.
// Changes only the background.
<Button className="bg-destructive">Delete</Button>

This is why you rarely need to restate a component's own styling to change one thing about it.

Compound parts take their own

Anything built out of parts styles each part separately, which is usually more precise than styling the root:

<Card className="gap-0">
  <Card.Header className="pb-2">
    <Card.Title className="text-lg">Invoice</Card.Title>
  </Card.Header>
  <Card.Content className="pt-0">…</Card.Content>
</Card>

The parts a component has are listed under Composition on its page.

When to use style

style wins over className — Uniwind puts the resolved classes first in the style array and your style prop after, so the later one lands on top.

Reach for it when the value is not a class: a computed dimension, an interpolated colour, a font family, anything coming out of a measurement.

<View style={{ width: containerWidth * 0.6 }} className="rounded-xl bg-card" />

Animated properties are not yours to set

Some components animate a property on the UI thread — an Input's border colour while it takes focus, a Slider's fill width, a Switch's thumb offset. A className setting that same property is overwritten on the next frame, so it looks like it did nothing.

Those components expose the values as props instead. If a class appears to be ignored, check the component's page for a prop that owns it.

cn for conditional classes

The same helper the library uses internally, exported for yours. It is clsx and tailwind-merge together, so conditions and conflict resolution both work:

import { cn } from 'panelui-native';

<Item
  className={cn(
    'rounded-xl',
    selected && 'bg-accent',
    disabled && 'opacity-50'
  )}
/>

Building on top

For a variant of your own, wrap the component with tailwind-variants — the same tool the library's own variants are built with:

import { Button, type ButtonProps } from 'panelui-native';
import { tv, type VariantProps } from 'tailwind-variants';

const promoButton = tv({
  base: 'rounded-full',
  variants: {
    tone: {
      launch: 'bg-info',
      sale: 'bg-warning',
    },
  },
  defaultVariants: { tone: 'launch' },
});

type PromoProps = Omit<ButtonProps, 'variant'> & VariantProps<typeof promoButton>;

export function PromoButton({ tone, className, ...props }: PromoProps) {
  return <Button {...props} className={promoButton({ tone, className })} />;
}

Declare the tv() at module scope, never inside the render — it is a builder, and rebuilding it every frame is pure overhead.

Colours belong to tokens

Anywhere you would write a colour, write the token instead: bg-primary, not bg-neutral-800. A hardcoded value stops following the theme and becomes something you find again in dark mode. Colors has the full list, and useCSSVariable for the cases where a class will not do.

Native controls ignore all of this

A component rendered with native hands off to SwiftUI or Jetpack Compose, and the platform owns what comes back — its colours, its metrics, its shape. className does nothing to it, and neither does a theme token.

// The className is not an error, and it is not applied either.
<Button native glass className="bg-primary">Continue</Button>

Anything the look depends on has to be a prop, and the spacing around it has to come from the container. See Native.

Next

On this page