-
-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Description
Describe the problem
Utility-first CSS frameworks like Tailwind use very granular CSS classes (e.g. bg-red-500
for a red background, shadow-lg
for a large box-shadow, ...). You often want to apply styles conditional with the class:
directive. Unfortunately, it only works for a single class at the moment which means you have to duplicate it quite often. A very simple example for a Button component with Svelte and Tailwind might look like this:
<script lang="ts">
export let color: 'primary' | 'danger' = 'primary';
</script>
<button
on:click
class="px-3 py-2 text-white rounded shadow-lg focus:outline-none focus:ring-2 focus:ring-offset-2"
class:bg-blue-700={color === 'primary'}
class:hover:bg-blue-800={color === 'primary'}
class:ring-blue-400={color === 'primary'}
class:bg-red-600={color === 'danger'}
class:hover:bg-red-700={color === 'danger'}
class:ring-red-500={color === 'danger'}
>
<slot />
</button>
This is very boiler-plate-heavy and annoying to work with. It's also just a very simple example for showcasing and usually gets even uglier in real-world examples. When using a utility-first framework you run into this issue a lot.
Describe the proposed solution
Allow the use of multiple CSS classes in the class:
directive with a class:"x y z"={true}
syntax. This would allow the example above to be simplified like this:
<script lang="ts">
export let color: 'primary' | 'danger' = 'primary';
</script>
<button
on:click
class="px-3 py-2 text-white rounded shadow-lg focus:outline-none focus:ring-2 focus:ring-offset-2"
class:"bg-blue-700 hover:bg-blue-800 ring-blue-400"={color === 'primary'}
class:"bg-red-600 hover:bg-red-700 ring-red-500"={color === 'danger'}
>
<slot />
</button>
Alternatives considered
There's been a very similar issue (#3376) which unfortunately has been closed and not been re-opened despite getting a lot of follow-up comments that argue for its usefulness. In this issue, some alternatives have been discussed:
Using Tailwind's @apply
directive
Tailwind does provide a @apply
directive to extract multiple Tailwind-classes into a custom CSS class. For the example above, this could look like this:
<script lang="ts">
export let color: 'primary' | 'danger' = 'primary';
</script>
<button
on:click
class="px-3 py-2 text-white rounded shadow-lg focus:outline-none focus:ring-2 focus:ring-offset-2"
class:primary={color === 'primary'}
class:danger={color === 'danger'}
>
<slot />
</button>
<style lang="postcss">
.danger {
@apply bg-red-600 hover:bg-red-700 ring-red-500;
}
.primary {
@apply bg-blue-700 hover:bg-blue-600 ring-blue-400;
}
</style>
While this appears to be a good solution (and is used by many to circumvent the issue), using the @apply
directive goes against the utility-first workflow. Adam Wathan (the creator of Tailwind) advised against using it (Source):
Confession: The
apply
feature in Tailwind basically only exists to trick people who are put off by long lists of classes into trying the framework.You should almost never use it 😬
Additionally, there are other Utility-CSS frameworks that usually don't have this feature.
Using the ternary operator
<script lang="ts">
export let color: 'primary' | 'danger' = 'primary';
</script>
<button
on:click
class="px-3 py-2 text-white rounded shadow-lg focus:outline-none focus:ring-2 focus:ring-offset-2
{color === 'primary' ? 'bg-blue-700 hover:bg-blue-800 ring-blue-400' : ''}
{color === 'danger' ? 'bg-red-600 hover:bg-red-700 ring-red-500' : ''}
"
>
<slot />
</button>
This does work, but obviously also introduces a lot of boilerplate code. The whole point of the class:
directive is to eliminate this kind of code.
Writing a Svelte Preprocessor
I'm not familiar with preprocessors, but this has been a frequent suggestion in the original issue. There even exists one already: https://github.com/paulovieira/svelte-preprocess-class-directive
This might be a viable option but I would much rather prefer support out-of-the-box instead of relying on a third-party library. Besides not being actively maintained, the linked preprocessor uses an alternative, non-ideal syntax like described in the next section "Alternative syntaxes".
Alternative syntaxes
Many other syntaxes have been suggested, e.g. class:x,y,z={true)
, .x.y.z={true}
, class:{"x y z"}={true}
, ...
The problem with most of them is that they either introduce breaking changes (e.g., class:x,y,z={true}
is already valid syntax for the class x,y,z
) and/or limit it to a sub-set of CSS-classes because ,
and .
are valid characters in CSS class names. While not very common in "classic" CSS classes, they are often used by utility frameworks like Tailwind (e.g. gap-[2.75rem]
, grid-rows-[200px_minmax(900px,_1fr)_100px]
, or row-[span_16_/_span_16]
). class:{"x y z"}={true}
would work and should be supported as an alternative syntax (just like class={"x y z"}
also works) but is also unnecessary (yet small) boilerplate in most cases.
Importance
would make my life easier
Final words
As mentioned above, this is technically a duplicate of #3376. However, since there have been no responses from any maintainers (even when pinging them) on the original issue, I've decided to open this issue with a summary of the discussion in the original issue. I would very much appreciate a re-evaluation of the original decision to not support this feature, either in this issue or by re-opening the original one. Thank you for the awesome work on Svelte!