Skip to main content

Styling components

Knowledge of style sheets is required.

Components are styled with the useStyles() hook (preferred) or the withStyles() higher-order-component. Both APIs require a style sheet that is conditionally rendered to CSS to generate atomic class names.

To continue with the example found in the style sheet documentation, let's design and style a button component. The button file would look something like the following.

import React from 'react';
import { createComponentStyles, CommonSize, PaletteType } from '@aesthetic/react';
export const styleSheet = createComponentStyles((css) => ({
button: css.mixin('reset-button', {
display: 'inline-flex',
padding: css.var('spacing-df'),
textAlign: 'center',
'@variants': {
'size:sm': { fontSize: css.var('text-sm-size') },
'size:df': { fontSize: css.var('text-df-size') },
'size:lg': { fontSize: css.var('text-lg-size') },
'palette:brand': { backgroundColor: css.var('palette-brand-bg-base') },
'palette:positive': { backgroundColor: css.var('palette-positive-bg-base') },
'palette:warning': { backgroundColor: css.var('palette-warning-bg-base') },
},
}),
button_disabled: {},
button_selected: {},
before: {},
after: {},
// ...
}));
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
afterIcon?: React.ReactNode;
beforeIcon?: React.ReactNode;
children: NonNullable<React.ReactNode>;
disabled?: boolean;
palette?: PaletteType;
selected?: boolean;
size?: CommonSize;
}
import { useStyles } from '@aesthetic/react';
export default function Button({
children,
beforeIcon,
afterIcon,
disabled,
selected,
palette,
size,
...props
}: ButtonProps) {
const cx = useStyles(styleSheet);
return (
<button type="button" {...props} className={cx('button')} disabled={disabled}>
{beforeIcon && <span className={cx('before')}>{beforeIcon}</span>}
{children}
{afterIcon && <span className={cx('after')}>{afterIcon}</span>}
</button>
);
}

If you're designing components for a library that'll be consumed externally, we suggest exporting the style sheet so that consumers may customize variants.

Generating class names#

Both the hook and HOC APIs provide a class name generation function that we aptly name cx. This function requires a list of selector names (keys from the style sheet object) in which to render CSS and apply a class name with, for example.

function Button({
children,
beforeIcon,
afterIcon,
disabled,
selected,
palette,
size,
...props
}: ButtonProps) {
const cx = useStyles(styleSheet);
return (
<button
type="button"
{...props}
className={cx('button', selected && 'button_selected', disabled && 'button_disabled')}
disabled={disabled}
>
{beforeIcon && <span className={cx('before')}>{beforeIcon}</span>}
{children}
{afterIcon && <span className={cx('after')}>{afterIcon}</span>}
</button>
);
}

As demonstrated above, the button selector will always be rendered. However, the button_selected and button_disabled selectors will only be rendered when the button is conditionally updated via the selected and disabled props respectively.

We can take this a step further by supporting variants. All that's required is to pass an object as the 1st argument to cx() with the name of every variant, and the variant to activate.

function Button({
children,
beforeIcon,
afterIcon,
disabled,
selected,
palette = 'primary',
size = 'df',
...props
}: ButtonProps) {
const cx = useStyles(styleSheet);
return (
<button
type="button"
{...props}
className={cx(
{ size, palette },
'button',
selected && 'button_selected',
disabled && 'button_disabled',
)}
disabled={disabled}
>
{beforeIcon && <span className={cx('before')}>{beforeIcon}</span>}
{children}
{afterIcon && <span className={cx('after')}>{afterIcon}</span>}
</button>
);
}

NOTE: The order of selectors passed to cx() does not determine the specificity. The order they are defined in the style sheet does! Be aware of this when developing.

Custom class names#

You can pass non-Aesthetic class names to cx() by passing an array.

cx('button', ['custom-class-name']);

Alternative styling#

Besides the useStyles hook, there is a special useCss hook, which is a low-level alternative for rendering a basic rule object and returning a class name.

import React, { useMemo } from 'react';
import { useCss } from '@aesthetic/react';
function Button({ children }: ButtonProps) {
// Memoize the styles to avoid unnecessary renders
const styles = useMemo(
() => ({
display: 'inline-flex',
padding: 8,
textAlign: 'center',
}),
[],
);
// Immediately render and return a class name
const className = useCss(styles);
return (
<button type="button" className={className}>
{children}
</button>
);
}

Be aware that this hook does not use the component style sheet format and instead uses plain style objects! Because of this, the following features are no longer available:

  • Theme object and utility functions
  • Structured style blocks
  • Variant styles
  • Styling multiple elements
  • Re-rendering when theme or direction change
  • Class name composition
  • High-performance and caching
  • And other design system related features...