A comparison of CSS handling in popular JS frameworks
Tags: vue, svelte, solidjs, css
Published: 2023-03-28 13:58:54

Having been a Vue developer since the early Vue 2 versions has't prevented me from diving a bit deeper into a comparison of the trending new JS frameworks, especially Svelte and Solid which I both love for implementing the disappearing framework paradigm, pioneered by Svelte, refined by Solid, and announced to be available soon in Vue as well as Vapor mode. It's funny how so many developers say they prefer Svelte to the alternatives just because of it's elegant API, and Solid just because of the signals (available in Vue long before). Feels quite superficial to me ... But anyway, that's another story. Today I want to highlight one thing that in my opinion is a really strong argument to prefer Vue. Feel free to find it just as superficial ;-)

Imagine you have two components where one uses the other one. You want the root element of the consumed (child) component to come with an own class but be able to add classes from the consuming (parent) component

## Class merging in Vue

It is as simple as this in Vue

```vue
// ComponentA.vue
<script setup>
import ComponentB from './ComponentB.vue'
</script>

<template>
<ComponentB class="a" />
</template>

// ComponentB.vue
<template>
<div class="b">Hello B</div>
</template>
```

Will lead to the desired html

```html
<div class="a b"></div>
```
This happens thanks to the fact that any non-prop attribute passed to a component will be applied to the root tag of the used component plus some extra logic for the class attribute. It's worth noting that this behavior is highly customizable, for those who don't like it.

## CSS class merging in Svelte

If you try to do the same thing in Svelte, it simply won't work

```html
// ComponentA.svelte
<script>
  import ComponentB from './ComponentB.svelte'
</script>

<ComponentB class="a" />

// ComponentB.svelte
<div class="b">Hello B</div>
```

Typescript tells you why (if you are using it)
`Type 'string' is not assignable to type 'never'`
In other words, `class` is not a prop key of `ComponentB`

So you would like to fix it like this
```html
// ComponentB.svelte
<script>
export let class;
</script>

<div class="b">Hello b</div>
```
But that doesn't work, `class` is a keyword in Javascript!

So what you have to do is change the prop name, let's follow a popular frameworks' naming:

```html
// ComponentA.svelte
<script>
  import ComponentB from './ComponentB.svelte'
</script>

<ComponentB className="a" />

// ComponentB.svelte
<script>
export let className = '';
</script>

<div class="b">Hello b</div>
```

That still doesn't work (and wouldn't even work if `class` was not a keyword) since we have to merge the classes now.
I switch to typescript so that we don't forget to mention that in TS land you also have to tell the compiler it's a string, unless, as in this case, you have the empty string as default value and get inference as a nice side effect.

```html
// ComponentB.svelte
<script lang="ts">
  export let className: string = ''
</script>

<div class={`${className} b`}>Hello b</div>
```

Quite some boilerplate, isn't it? Sure, if boilerplate does not scare you and you like the explicitness about where the props come from and where they go, no problem. But what if you have to consume someone else's component, imagine `ComponentB` coming from a library and having to author `ComponentA`. You can sure say that a component library that does not offer any API to add CSS classes to its components is flawed and that pull requests and issues can be filed, but this is always a slow process.

## CSS class merging in Solid

The situation here is not essentially different from Svelte.

```tsx
const ComponentB = (props: { class?: string }) => {
  return <div class={`${props.class || ''} b`}>Hello b</div>
}

const ComponentA = () => {
  return <ComponentB class="a" />
}
```
At least we may call it `class` here.

## The solutions in a popular component library

The same problem applies to the style attribute for both Svelte and Solid (and also React, by the way), whereas Vue again smartly auto-merges with well defined behavior.

In MUI, the popular React material components library, the customization problem for inline styles is solved with the `sx` property. But try to add a class to the root element to an MUI component ... not so straight forward.

## Conclusion

If you, like me, think that speed of development when adding classes to html tags is more important than having total explicitness about the props that determine the CSS of your components at the price of more boilerplate, you should consider this an important difference when choosing your framework for your next web app. Personally, I have never experienced that it was hard to debug where CSS classes or styles came from in Vue apps, so I can't imagine any benefit in doing it the way other frameworks do it.