sindresorhus / modern-normalize

🐒 Normalize browsers' default style
MIT License
6.16k stars 358 forks source link

Consider a version without forcing box-sizing #48

Open rjgotten opened 3 years ago

rjgotten commented 3 years ago

Counter to the common trend, it is not always 'awesome' or 'FTW'. It wreaks havoc with expectations stemming from normal browser defaults, which makes it harder to integrate into existing properties and makes it harder to integrate anything created by third parties that are not operating off of the same opinionated principle.

It's also a total pain in the ass to selectively undo with override rules, as it's reapplied on every element. At the very least you could use box-sizing: inherit I'd think, which makes it at least somewhat easier to selectively cut this awesomely annoying behavior out?

franktopel commented 3 years ago

Really, Chris Coyier has improved the code that is currently being used in modern-normalize.css back in 2014.

Instead of

*, *::before, *::after {
  box-sizing: border-box;
}

we really

should be getting this:

html {
  box-sizing: border-box;
}

*, *::before, *::after {
  box-sizing: inherit;
}

I can't explain better than Chris, so please take the time to read his reasoning why this is a much better version: https://css-tricks.com/inheriting-box-sizing-probably-slightly-better-best-practice/

Even Paul Irish, who in 2012 came up with the code you're currently delivering, has agreed and edited his original article to reflect the improvement: https://www.paulirish.com/2012/box-sizing-border-box-ftw/

franktopel commented 3 years ago

I've created a PR for this improvement. https://github.com/sindresorhus/modern-normalize/pull/56

nuxodin commented 3 years ago

This is also an option, but I dont think its always the better one. In most cases I want box-sizing:border-box. But there is also the situation that I want content-box for a certain element, but not inherited to the contained elements.

Both have advantages and disadvantages, for me not inheriting is the better solution.

rjgotten commented 3 years ago

@nuxodin Both have advantages and disadvantages, for me not inheriting is the better solution.

Even when inheriting, your use-case can still be supported. Just reset back to border-box on the direct children.

Personally, I would just pre-process whatever form of box-sizing normalization is present in a normalization sheet and rip it out, because after having run up against problems with it numerous times, I've come to believe it to be a theoretical ideal but a fallacy in practice...

sindresorhus commented 3 years ago

The current version was intentional: https://github.com/sindresorhus/modern-normalize/pull/37

// @jamiebuilds

jamiebuilds commented 3 years ago

Yeah, I think the advice in css-tricks is only better if you only consider one pattern of building UIs.

Compare these two snippets:

Islands of widgets:

<widget-one/>
<widget-two/>
<widget-three/>

Trees of components:

<component-one/>
<compontent-two>
  <component-three/>
</component-two>

Both of these have their place on the web, and really almost every webpage will be a mix of both of these in different places. So given the goals of a generic reset/normalize stylesheet, they should both be considered.

The idea behind applying box-sizing: inherit to every element is that it creates a simpler mental model when marking an entire sub-tree as box-sizing: content-box.

That's great if you're only considering the "islands of widgets" pattern, but then it shifts the burden onto the "trees of components" pattern. And you start getting snippets like this:

component {
  box-sizing: content-box;
}

component::before, component::after, component > * {
  box-sizing: border-box;
}

So really we just have a tradeoff here, if we make it easier for "islands of widgets" we make it harder for "trees of components", if we make it easier for "trees of components" we make it harder for "islands of widgets".

So the question becomes: Which is the easier mental model for people to apply in the real world?

*, ::before, ::after {
  box-sizing: border-box;
}

Developer: "I want to make this entire tree content-box sized because that's what its expecting"

widget, widget *, widget ::before, widget ::after {
  box-sizing: content-box;
}

That's it, they are done.

<component-one>
  <component-two>
    <component-three/>
  </component-two>
  <component-four/>
</component-one>
:root {
  box-sizing: border-box;
}

*, ::before, ::after {
  box-sizing: inherit;
}

Developer 1: "I want my component to be sized with content-box"

component-one {
  box-sizing: content-box;
}

Developer 2: "Wait why does my component suddenly look wrong. Oh its inheriting content-box now, I guess I need to reset my component, and I'll make sure nothing inherits content-box again":

component-two, component-two *, component-two ::before, component-two ::after {
  box-sizing: border-box;
}

Developer 3: "Wait why does my component suddenly look wrong. Oh someone added a higher specificity selector overriding my content-box. I'll just add !important

compontent-three {
  box-sizing: content-box !important;
}

Developer 4: "Wait..."

I've seen all of this chaos play out all because Developer 1 forgot to reset the styles back to border-box for <component-one/>'s children. I've also seen it play out that Developer 1 reset the styles incorrectly and broke the inherit behavior for everyone.

Overall, I think a generic reset/normalize stylesheet is better off pushing the burden on "islands of widgets" because a lot less can go wrong there.

rjgotten commented 3 years ago

@jamiebuilds That's it, they are done.

You're conveniently leaving out the case where .widget nests something else which needs box-sizing:border-box and which is styled using best-practice short class-only selectors, e.g.

<div class="widget">
  <div class="nested-widget"></div>
</div>
*, ::before, ::after {
  box-sizing: border-box;
}

.widget, .widget *, .widget ::before, .widget ::after {
  box-sizing: content-box;
}

.nested-widget {
  box-sizing: border-box;
}

That will break and will require the site author to pinpoint target their own dedicated CSS rules with higher specificity to fix it. Moreover; it may require the site author to do so, each and every time the nested widget structure changes and those changes may be out of their control if they are dynamically including a third party widget from another web origin not under their control.

This is the reason both implementations of 'border-box all the things' are flawed, btw. And the reason a version without it should exist.

jamiebuilds commented 3 years ago

which is styled using best-practice short class-only selectors

That problem is only difficult because you decided that this was a best practice. Maybe re-evaluate that.

rjgotten commented 3 years ago

That problem is only difficult because you decided that this was a best practice. Maybe re-evaluate that.

Oh yes; let's force site authors to needlessly write higher-specificity selectors to cater to the whims of forcing non-default behavior on box-sizing. And then let's cover up that mess by saying those site authors should re-evaluate their position on the practice of managing selector specificity to keep it low.

Now there's a clear-cut case of "I reject your reality and substitute my own."

nuxodin commented 3 years ago

With css variables you could have the advantages of both variants, but maybe that's too mutch overhead.

html {
  --box-sizing:border-box;
}
*, ::before, ::after {
  box-sizing: var(--box-sizing);
}

.elementNeedsContentBox { /* but does not inherit */
   box-sizing: content-box;
}
.widget {
  --box-sizing: content-box; /* will inherited */
}
.nested-widget {
  --box-sizing: border-box; /* will inherited */
}