Good to knowReact Fundamentals · 06

Styling

className vs style objects, a Tag component that wraps a span, and why the order of a spread decides who wins.

On this page · 8 sections
  1. In one minute
  2. The example: a Tag for product labels
  3. How it works
  4. Who wins: order of the spread
  5. Common mistakes
  6. Interview Q&A
  7. Related
  8. Sources

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

Who wins: order of the spread

{ fontWeight: 600, ...style }
Tag.tsx
1function Tag({ style, ...rest }) {2  return <span style={{3    fontWeight: 600,4    ...style,5  }} {...rest} />6}
What you see
<Tag style={{ fontWeight: 400, marginLeft: 8 }}>
style = ?
  1. fontWeight: 600,The component’s default is written first.
  2. ...style,The caller’s keys are copied after it. For the same key, the later one replaces it.
  3. commit phaseThe caller can override the default. Recorded HTML: style="font-weight: 400; margin-left: 8px;".

0 / 0
Object spread follows one rule: for the same key, whatever comes later wins. Props spread in JSX works the same way.

{ 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

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.

Sources

Primary source: react.dev/reference/react-dom/components/common#applying-css-styles