Skip to content

[Feature Proposal] Stack utilities #1570

@mainej

Description

@mainej

It is common to want a border between each of the elements in a list. The Tailwind documentation recommends adding borders to each element but the first or last, using the :first-child variant:

<div>
  <div v-for="item in items" class="border-t first:border-t-0">
    {{ item }}
  </div>
</div>

By using the "lobotomized owl" selector, as introduced here and elaborated on here, it is possible to write HTML that looks like this:

<div class="stack-border-y">
  <div v-for="item in items">
    {{ item }}
  </div>
</div>

The border class is moved from the component elements to the surrounding context. This reduces the number of classes necessary. To me, it also conveys the intention of the design better: "this is a vertical stack of elements with a border between each of them."

This is applicable to Tailwind UI. Obviously, it applies to bordered lists. In the case of panels/cards, it is especially useful in procedurally generated HTML, when a panel will sometimes have multiple sections, but other times not.

Margin and padding are very similar—often you want the same amount of space between a series of n elements. This is most easily configured on the context surrounding the elements, rather than on each element but the first or last. Otherwise, you can accidentally end up with different amounts of space between elements, or with spacing bugs when the order of elements is switched.

To support these use cases, I've been adding the following stack-* plugins to my projects. I propose they could be promoted to core plugins.

{
    plugins: [
        plugin(function ({ addUtilities, e, theme, variants }) {
            const utilities = _.flatMap(theme('padding'), (size, modifier) => ({
                [`.${e(`stack-my-${modifier}`)} > * + *`]: { marginTop: `${size}` },
                [`.${e(`stack-mx-${modifier}`)} > * + *`]: { marginLeft: `${size}` },
                [`.${e(`stack-py-${modifier}`)} > * + *`]: { paddingTop: `${size}` },
                [`.${e(`stack-px-${modifier}`)} > * + *`]: { paddingLeft: `${size}` },
            }));
            addUtilities(utilities, variants('padding'));
        }),
        plugin(function ({ addUtilities, e, theme, variants }) {
            const generator = (value, modifier) => ({
                [`.${e(`stack-border-y${modifier}`)} > * + *`]: { borderTopWidth: `${value}` },
                [`.${e(`stack-border-x${modifier}`)} > * + *`]: { borderLeftWidth: `${value}` },
            });
            const utilities = _.flatMap(theme('borderWidth'), (value, modifier) => {
                return generator(value, modifier === 'default' ? '' : `-${modifier}`);
            });
            addUtilities(utilities, variants('borderWidth'));
        }),
    ],
}

If you'd like, I'd be willing to put together a PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions