Luciano Battagliero

§Attribute-Derived Styling

Published on

In a component, state is often expressed twice: once for behavior, and again for presentation.

To reduce the chance of both falling out of sync, we can reflect state directly in the DOM using aria-* and data-* attributes, then derive styles from them.

§Making Use of Existing State

ARIA attributes expose state to assistive technologies. Common examples include aria-selected, aria-expanded, and aria-disabled.

For concepts ARIA doesn’t cover, data-* attributes can express state explicitly, like data-open, data-closed, or data-highlighted.

When styles derive from these attributes, the DOM becomes the single source of truth.

§Prop-Derived vs Attribute-Derived

Consider a component where animations change based on its position and whether it’s open or closed.

A common approach is to derive styles directly from props:

const Component = ({
  open,
  position,
  ...props
}: ComponentProps) => {
  return (
    <div
      className={cn(
        'rounded-md bg-white p-2 shadow-sm',

        open ? 'animate-in fade-in-0' : 'animate-out fade-out-0',

        position === 'top' && 'slide-in-from-top-2',
        position === 'bottom' && 'slide-in-from-bottom-2',
        position === 'left' && 'slide-in-from-left-2',
        position === 'right' && 'slide-in-from-right-2'
      )}
      {...props}
    />
  );
};

Alternatively, state can live in the DOM and styles derive from it:

const Component = ({
  open,
  position,
  ...props
}: ComponentProps) => {
  return (
    <div
      data-open={open || undefined}
      data-closed={!open || undefined}
      data-position={position}
      className='
        rounded-md bg-white p-2 shadow-sm

        data-[open]:animate-in
        data-[open]:fade-in-0
        data-[closed]:animate-out
        data-[closed]:fade-out-0

        data-[position=top]:slide-in-from-top-2
        data-[position=bottom]:slide-in-from-bottom-2
        data-[position=left]:slide-in-from-left-2
        data-[position=right]:slide-in-from-right-2
      '
      {...props}
    />
  );
};

State expressed once, styles derived from it. Nothing to keep in sync.

Filed under #technical

Subscribe to the RSS feed