blog

Override default utility class in Preact component

At work, I combine both BEM and utility classes (similar to Tailwind) in my CSS. I find that works best for me.

I have a Button component in Preact and a matching .c-btn class in my stylesheet. Since using buttons inside a form is a common use case for me, I extended my default Button component to create FormButton.

const FormButton = (props) => (
<Button
{ ...props }
>

{ props.title || 'Submit' }
</Button>
);

For my form button, I didn’t feel like creating .c-btn--form. The only style difference I wanted is consistent vertical spacing. So I used utility class to set margin-bottom.

const FormButton = ({ title, className, ...rest }) => (
<Button
className={ classnames(className, 'u-mb-4') }
{ ...rest }
>

{ title || 'Submit' }
</Button>
);

I use classnames library to manage classes on my components. Here, I set the default .u-mb-4 class on FormButton but still allow additional classes to be passed via props.

But what if I wanted to override margin-bottom? In HTML, order of classes doesn’t impact specificity. I would have to be sure my overriding class comes later in stylesheets or have higher specificity than .u-mb-4. Neither was ideal.

To avoid such problems, I instead removed .u-mb-4 class if another utility class of such type was passed.

const FormButton = ({ title, className, ...rest }) => (
<Button
- className={ classnames(className, 'u-mb-4') }
+ className={ classnames(className, { 'u-mb-4': !className.includes('u-mb') }) }
{ ...rest }
>
{ title || 'Submit' }
</Button>
);

Now, my default class is only applied if there is no other similar class passed in props. I could do this since all utility classes that set margin-bottom have a similar name that goes like u-mb-N where N is a number that represents spacing multiplier (ie. 1 is 5px, 2 is 10px...). Using String.includes() I check whole classNames prop for a matching class and using classnames lib I can conditionally set my default class.