In one minute
React has two built-in ways to style: className (CSS classes, the usual choice) and style (inline styles as a JavaScript object, camelCased: backgroundColor, marginLeft). Numbers get px for you. The pattern worth knowing is a small component that wraps a native element, offers a typed prop like tone="sale" instead of raw class names, and still lets callers add their own className and style.
You’ll be able to: write such a wrapper, and decide who wins when defaults and caller styles collide.
The example: a Tag for product labels
type Tone = 'sale' | 'new' | 'soldout'
function Tag({ tone, className, style, ...rest }: ComponentProps<'span'> & { tone?: Tone }) {
return (
<span
className={['tag', tone && `tag--${tone}`, className].filter(Boolean).join(' ')}
style={{ fontWeight: 600, ...style }}
{...rest}
/>
)
}
Used like this:
<Tag tone="sale">-20%</Tag>
<Tag tone="soldout" className="muted" style={{ fontWeight: 400, marginLeft: 8 }} title="Back soon">
Sold out
</Tag>
The HTML React produced (recorded):
<span class="tag tag--sale" style="font-weight: 600;">-20%</span>
<span class="tag tag--soldout muted" title="Back soon" style="font-weight: 400; margin-left: 8px;">Sold out</span>
How it works
styleis an object.style={{ fontWeight: 600 }}isn’t special syntax: the outer braces start a JSX expression, the inner ones are an object literal.marginLeft: 8becamemargin-left: 8px.- A typed
tonehides class names. Callers writetone="sale"; onlyTagknows the class istag--sale. You can rename classes without touching callers, and they get autocomplete. classNameis merged, not replaced. The array/filter(Boolean)/join(' ')line combines the base class, the tone class and the caller’s class. The tinyclsxlibrary does the same:clsx('tag', tone && `tag--${tone}`, className)....restpasses everything else through:title,children, event handlers,aria-*. That’s whytitle="Back soon"reached the<span>.
Who wins: order of the spread
1function Tag({ style, ...rest }) {2 return <span style={{3 fontWeight: 600,4 ...style,5 }} {...rest} />6}fontWeight: 600,The component’s default is written first....style,The caller’s keys are copied after it. For the same key, the later one replaces it.- commit phaseThe caller can override the default. Recorded HTML:
style="font-weight: 400; margin-left: 8px;".
1function Tag({ style, ...rest }) {2 return <span style={{3 ...style,4 fontWeight: 600,5 }} {...rest} />6}...style,The caller’s keys are copied first.fontWeight: 600,The default comes later, so it replaces the caller’s value.- commit phaseThe default can’t be overridden; the caller can only add other keys.
{ fontWeight: 600, ...style } lets the caller override the default (recorded: font-weight: 400). { ...style, fontWeight: 600 } would lock the default. Choose on purpose.
Common mistakes
style="font-weight: 600"(a string). React expects an object.className={tone}with raw class names leaking into every caller.- Overwriting the caller’s
classNameorstyleinstead of merging. - Forgetting
...rest, soaria-label,titleoronClicksilently disappear.
Interview Q&A
How does style differ between HTML and JSX?
In HTML it’s a CSS string; in JSX it’s an object with camelCased property names, and numbers get px added for most properties: style={{ marginLeft: 8 }} → margin-left: 8px.
How do you let callers override a component's default styles?
Accept className and style, merge them with your defaults, and put the caller’s values last: className={['tag', className].filter(Boolean).join(' ')} and style={{ fontWeight: 600, ...style }}. Later keys win.
Why expose a typed prop like tone instead of class names?
It’s a small, checked API: callers can’t depend on internal class names, typos are compile errors, and you can change the CSS without breaking them.
What problem does clsx solve?
Building a className from conditions without empty strings and double spaces: clsx('tag', { 'tag--sale': onSale }, className) instead of hand-written joins.
Related
- TypeScript with React:
ComponentProps<'span'> & { tone?: Tone }. - Using JSX: spread order in JSX props.
Sources
- react.dev: Applying CSS styles
- Topic order inspired by Kent C. Dodds’ EpicReact React Fundamentals workshop; the example app and code here are this handbook’s own.
Primary source: react.dev/reference/react-dom/components/common#applying-css-styles