Proposal: Tailwind Merge pattern for native Tailwind v4
@Hugos68 came across this new technique that could potentially allow us to rethink the way you apply classes to components via what we currently call style props.
https://x.com/diegohaz/status/1897776774206853269
We'll review this as a potential component API change for the next major release of Skeleton (v4).
What This Does
Currently we have the concept of style props in separate "channels" like: base | {property} | classes, but this would allow us to streamline this down to ONLY classes because base and property classes could be applied inline in the HTML template, but overwritten with classes that always take precedence without the need of third-party tools like Tailwind Merge or using !.
<div class="base:rounded-full base:bg-red-500 {classes}">...</div>
There's several clear benefits here:
- No need to abstract Tailwind classes to props - with a few potential exceptions (ex: state-based classes)
- No need to generate multiple canned style props - every property can be passed and just works
- Removed a Skeleton-specific concept you have to learn - just add classes and you're done
Real World Example
So here's a real world component example today:
<Avatar background="bg-green-500" classes="border border-white" />
Versus what it could be after the change:
<Avatar classes="bg-green-500 border border-white" />
What May Not Change
We would still need the prefix approach to target multiple elements within the component template. Like:
classes
imgClasses
fallbackClasses
We may also want to keep the term classes (plural) to avoid conflict with class. Though this can be discussed further.
Downsides / Pitfalls
Unfortunately this does have a slight cost to the final bundle size. Every time we use a native Tailwind utility like base:p-4, this does not resolve down to the same instance as p-4. So every base: class we add in our components would be a "dupe". But this is likely a worthwhile trade-off for the DX improvement added.
He's a trivial example using bg-red-500:
https://play.tailwindcss.com/5DRJbdc3HW
See Also:
- https://github.com/skeletonlabs/skeleton/issues/3320
I think we should move away from classes if we were to use this because classes isn't picked up by Tailwind Intellissen, if we did class and <element>Class (or imo, just use components and always use class) we could instruct users to only add *Class to their tailwind extension config.
My biggest concern with that was that class typically requires some kind of work around because it's a reserved name. We would work around that for the root element, but the each child would implemented differently. Where as if we just keep them custom props it's consistent across the board, across all frameworks. But I do think folks would respond better to the singular version.
No workarounds needed:
<script>
const props = $props();
</script>
<div class="base:bg-red-500 {props.class}">
<div class={props.labelClass}>test</div>
</div>
export default function(props) {
return (
<div className='base:bg-red-500 {props.class}'>
<div className={props.labelClass}>test</div>
</div>
);
}
This would work even better if we went with a component based approach (my favorite):
<script>
const props = $props();
</script>
<div {...props} class="base:bg-red-500 {props.class}">
{@render props.children?.()}
</div>
export default function(props) {
return (
<div {...props} className='base:bg-red-500 {props.class}'>
{props.children}
</div>
);
}
That's 1/4 frameworks. Now we need React, Vue, and Solid too.
@endigo9740 I showed you react and solid already, look again. Vue is the same aswell. Nothing changes.
I'm going to go ahead and close this out, as we have a definitive answer to this in the upcoming Skeleton v4 release. I'd recommend checking out the updated Fundamentals docs and/or Contributor > Component docs for details when v4 launches for more information.