linkedin / css-blocks

High performance, maintainable stylesheets.
http://css-blocks.com/
BSD 2-Clause "Simplified" License
6.33k stars 152 forks source link

Rules on universal selector (*) and combinors prevent using the 'lobotomised owl' selector (:scope > * + *) #508

Open JoshTumath opened 3 years ago

JoshTumath commented 3 years ago

I love the concept of CSS Blocks and would love to use it in my team's design system. However, it expressly forbids the use of a selector like :scope > * + * (known to some people as the lobotomised owl selector). I want to present our use-case to you and see what you think! Maybe this is a convincing argument for CSS Blocks to support the owl selector, or maybe there's an equivalent alternative way of doing the same thing in CSS Blocks that solves the same problem.

Use case: layout components

In a design system, you want components to be self contained and not have to worry about how they're laid out. We have a rule in our design system called 'No outer margins'. It should be possible to use a component anywhere without having to worry about where it will be used. If the component has a margin on the outside, it may have a gap around it where you haven't intended it.

So how do you set gaps and margins between components? In our design system, that's a the parent component's responsibility to compose them together. We call the components that have this responsibility 'layout components'.

For example, a layout component called Grid would use CSS Grid to lay out its children. And the gap property would set the gap between components. I wouldn't need to go outside of the scope of the Grid component to set the gap between child components because of the way CSS Grid works; that can be done from the parent.

/* Grid */
:scope {
  display: grid;
  gap: 1rem;
}

However, if I don't need to use a grid layout - if I just want a stack of paragraphs and images in an article to be laid out one-below-the-other - I just need the default CSS flow layout. So I need to use a margin to set a space/gap between components. That's where the owl selector comes in. I can create a layout component called Stack and it will set a gap between its children; I don't need to worry about what components those children are. (The Book 'Every Layout' makes a great case for the owl selector when explaining the Stack layout.)

<Stack>
  <h1>CSS is awesome</h1>
  <p>Foo</p>
  <p>Foo</p>
  <p>Foo</p>
</Stack>
/* Stack */
:scope {
  display: block;
}

:scope > * + * {
  margin-top: 1rem;
}

It's a similar problem if you're using CSS Flex to create a layout, and you need to add the flex property to the children to set varying flex amounts.

lougreenwood commented 3 years ago

Would it be possible to use :scope > *:not(:last-child) instead?

JoshTumath commented 3 years ago

Would it be possible to use :scope > *:not(:last-child) instead?

Yeah that would do the same thing. However, I would have thought that be against the rules of the system as well, since the universal selector is forbidden. Wouldn't the :not pseudo-class have to be associated with a particular class (:scope > .foo:not(:last-child))?

With a layout component, you don't know what the classes of the child elements are going to be. 🙂