-
Notifications
You must be signed in to change notification settings - Fork 4.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add space
and divide
utilities
#1584
Conversation
For Alpine.js compatibility, sad because it increases the specificity but I don't think it will actually be a problem at all in real projects.
Cool as hell. Can't wait to use it! |
You really put a lot of thinking into this. I like that implementation. 👏👏👏👏👏 |
@adamwathan Am I correct that the Overall I think this is a nice addition to Tailwind but I'm a bit concerned with the number of potential cases where the abstraction breaks. I already told you about this on Discord some time ago, but here are some of them:
|
Proposal
Instead of Examples <ul>
<li>One</li>
<li class="mt-4">Two</li>
<li class="mt-4">Three</li>
</ul>
<!-- Now, using `gap` utilities -->
<ul class="gap-y-4">
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>
|
@sir-kain |
Correct, I can't see how you could ever really use these together or what the expected result would be really (would If you want to add space around items that have dividers between them, you'd use vertical padding on each item, just like you would if you were doing the borders manually 👍
Yeah this is an annoying one and in my opinion not something we could ever handle internally in Tailwind aside from using Example for those curious: To me that's like a 1% scenario though, 99% of the time I just want to space things out in a single row or column (like links in a nav bar), so to block this feature just because of the wrapping thing seems unnecessary, especially because this doesn't break anything related to that situation, it just doesn't solve it just like adding For cases where you need the wrapping behavior, you'd have to do the negative margin trick and build it sort of like a flexbox grid (I know you know this but leaving the example for others 👍 ) <div class="flow-root bg-blue-100">
<div class="-m-2 flex flex-wrap max-w-md">
<div class="p-2">
<span class="rounded-full bg-blue-500 text-white px-3 py-1">One</span>
</div>
<div class="p-2">
<span class="rounded-full bg-blue-500 text-white px-3 py-1">Two</span>
</div>
<div class="p-2">
<span class="rounded-full bg-blue-500 text-white px-3 py-1">Three</span>
</div>
<div class="p-2">
<span class="rounded-full bg-blue-500 text-white px-3 py-1">Four</span>
</div>
<div class="p-2">
<span class="rounded-full bg-blue-500 text-white px-3 py-1">Five</span>
</div>
<div class="p-2">
<span class="rounded-full bg-blue-500 text-white px-3 py-1">Six</span>
</div>
<div class="p-2">
<span class="rounded-full bg-blue-500 text-white px-3 py-1">Seven</span>
</div>
<div class="p-2">
<span class="rounded-full bg-blue-500 text-white px-3 py-1">Eight</span>
</div>
<div class="p-2">
<span class="rounded-full bg-blue-500 text-white px-3 py-1">Nine</span>
</div>
</div>
</div> The annoying thing about this is all the extra wrapper elements you need, which is fine when you actually have this wrapping situation and you have no other choice, but to always do this even when the content will never wrap just because it's the "lowest-common-denominator" solution is really over-engineering in my opinion.
Yeah this is another valid edge-case but again I don't think we have to throw away the idea just because it doesn't solve every possible situation. Long-term,
This is a very interesting point — I wonder if it would be weird to use This also makes me realize this solution breaks when you do I still think the utilities are very valuable but it is unfortunate that there are holes like this and makes me even more desperate for universal flexbox gap support 😩 One more thing I'm worried about — the increased specificity of these selectors didn't seem like a problem to me at first but now I'm wondering if they are a problem when doing responsive stuff, because if you do Can anyone think of a concrete example where they would've been bitten by this? |
Unfortunately, not for flow layout (inline and block). I imagine this will be used a lot on regular, non-flex-or-grid layouts like this: <ul class="space-y-4">
<li class="block">One</li>
<li class="block">Two</li>
<li class="block">Three</li>
</ul> or even inline, like this: <footer class="space-x-4">
<a href="#">About Us</a>
<a href="#">Contact Us</a>
<a href="#">Privacy Policy</a>
</footer> The CSS Working Group is not planning on making I agree with you that these shortcomings are not bad enough to block this feature. And for
Not weird, but we'd lose IE11 support. Which I suppose is fine for spacing if it's fine for |
Does .space-x-1 > *:not(:last-child) { margin-right: 4px } |
The problem I’m trying to solve most is handling |
Space is not perfect but works great in a lot of case. What about adding this kind of component (like you do for forms)? |
The I would argue that this: .space-x-4 > :not(template) ~ :not(template) {} ...looks a lot more like a hack that a negative margin. Besides, it makes the selector quite large. Given that it's repeated for each space utility (and there are a lot of those), it could add up, even when minified. Anyway, these are the reasons I think negative margin approach is better:
Comparing this: .space-x-16 {
margin-left: -1rem;
}
.space-x-16 > * {
padding-left: 1rem;
} ...to this: .space-x-16 > :not(template) ~ :not(template) {
--space-x-reverse: 0;
margin-right: calc(1rem * var(--space-x-reverse));
margin-left: calc(1rem * calc(1 - var(--space-x-reverse)));
} For half the CSS, it handles more use cases. And we have to agree that at least it looks a lot less hacky, without all of that The obvious downside is that you might need another DOM element, but you also might not. Besides, why is that considered bad anyways? Having an element with with just the As @benface mentioned, though, |
The negative margin/padding approach has a few problems:
To me the need for wrapper elements to avoid the padding problem is a complete deal-breaker, just not worth it when all you are trying to do is put a bit of space between items that already exist. This is the CSS you'd actually need because of the reverse problem: .space-x-16 {
--space-x-reverse: 0;
margin-right: calc(-1 * 1rem * var(--space-x-reverse));
margin-left: calc(-1 * 1rem * calc(1 - var(--space-x-reverse)));
}
.space-x-16 > * {
--space-x-reverse: 0;
padding-right: calc(1rem * var(--space-x-reverse));
padding-left: calc(1rem * calc(1 - var(--space-x-reverse)));
} The size thing isn't a really serious problem, compression algorithms love duplication so the fact that Super appreciate the input and feedback but no plans to change the implementation of this, you can instead disable these plugins if you want and replace them with your own implementations using the plugin system. |
Thanks for this. I've never seen I'm wondering: do you know whether |
This PR adds support for new
space-{x/y}-{n}
,divide-{x/y}-n
, anddivide-{color}
utilities for adding space or borders between child elements.Examples
Adding 16px of vertical space between items:
Adding 8px of horizontal space between items:
Adding a 1px border between items:
Specifiying a border color:
Implementation
You'll often see classes like this implemented using the "lobotomized owl selector":
I had originally implemented these utilities this way as well, but ran into an interoperability problem with Alpine.js because of their use of the
<template>
tag to store markup snippets in the DOM.If they were doing something extremely weird I would likely just say "forget it" and use the owl selector instead, but I honestly think the fact that the browser even looks at template tags when evaluating CSS is borderline a bug, and since I personally really enjoy using Alpine, it seemed worth trying to fix if I could find a solution that I was happy with.
After trying a bunch of different things, I landed on this as the best solution:
This effectively ignores any template tags, allowing it to work even with markup as weird as this:
It unfortunately comes at the cost of increased specificity (0, 1, 2 instead of 0, 1, 0) which means you cannot override the styles applied by these classes with simpler utilities. For example, this will not work:
After trying to think through real-world scenarios where this would actually matter though, I wasn't able to come up with much. Even if you do run into this, you can always revert to the "old way" and not use these new classes at all in those situations.
This specificity problem isn't really new in Tailwind anyways — it's also present with the
group-hover
utilities which have a specificity of 0, 2, 0 while regularhover
utilities are 0, 1, 1.I have some ideas for tackling those problems in the future in a more general way if they really end up being problems for people, but so far it's only come up one time that I have ever seen.
These utilities have all been implemented as new core plugins, and all have their own new keys in the theme configuration:
space
divideWidth
divideColor
The
space
utility pulls fromspacing
by default,divideWidth
pulls fromborderWidth
, anddivideColor
pulls fromborderColor
.I considered having these plugins reference those keys directly instead of giving them their own keys that inherit from the existing keys (I even considered just adding these classes to the existing
margin
,borderWidth
, andborderColor
plugins) but ultimately it felt most flexible and consistent to give them their own keys, especially since for all intents and purposes end-users won't even know the difference between them having their own keys or sharing existing keys, since updates toborderWidth
for example will automatically apply todivideWidth
anyways.Only have "responsive" variants generated by default.
Why not a variant?
I considered implementing this as some sort of very generic
children-except-first
variant that you'd use like this:...but for these two extremely common problems (spacing things out or adding dividers), the syntax felt too obscure. We could still add something like that in the future if we really wanted, but I think from a pure developer experience perspective I prefer the spacing/dividers problem to have a more explicit solution.