The base problem here is that both a component and its host may want to style the component. The component model should account for this and offer some guidance.
Web components do this with the `:host` selector that styles the component from within its shadow root. The styles applied with the `:host` selector can be overridden by the outside styles, without the component needing to weaken encapsulation by allowing styling from the outside via JS properties. This means it's not really bad for a component to give itself default outside styling like block vs inline display, or even margins, because the user can always reset them as needed, much like built-in elements.
For instance, a web component might have these styles:
<my-element>
#shadow-root
:host {
margin: 1em;
}
And at its use-site, the margins can be set differently: <style>
my-element {
margin-botton: 0;
}
</style>
<h1>...</h1>
<my-element></my-element>
<div>...</div>
Shadow DOM also helps with specificity fights, as the cascade goes: shadow style -> light style -> light !important -> shadow !important. This way a component can define which styles are only defaults.Edit to clarify: one of the big problems with the article is the use of inline styles. They have the highest specificity and there's no built-in way to "merge" inline styles like you normally get via inheritance and the cascade.
I don't see how the React approach of accepting styles as JS props is any weaker in terms of encapsulation compared to your example above. If anything, it offers the opportunity for stronger encapsulation because it supports limiting/transforming the styling props we accept through those JS props, and even allows us to define a styling API for our component that's completely independent of CSS semantics, enabling components APIs that can span multiple platforms with independent implementations.
What makes me say that encapsulation is weakened is that threading JS property bags to other internal elements is also relatively common, and inline styles don't really offer any encapsulation there. Other selectors in the page can still select and style properties that are not directly styled by the element. I should have clarified that.
Shadow DOM provides true runtime style encapsulation, and the selectors on the page can't select the internal elements at all.
const MyComponent: React.FC<{style?:React.CSSProperties}> = (props) => <div style={{margin: 16, ...props.style}}>{props.children}</div>
I don't see any issue; additionally, it's well typed and you have fine control over it!Remember when we were building our apps as a set of screens and pages instead of thinking in components?
Sure, but I also remember building web apps in GWT, using a toolkit of reusable widgets, with an OO interface.
Edit to add: I guess I’m more agreeing with the author than arguing with them; I just find it amusing how what goes around comes around.
I saw it more as "purely functional", where components are the functions and styling outside of the component is a side-effect. Avoiding side-effects like margin or align-self makes components more like pure functions than objects IMO.
They’re awkward because they have bigger knock-on effects on the overall layout of components within the parent.
In an OO mindset, I see this as being all about what interfaces your object exposes. For something like a button or slider control, it obviously exposes an action callback of some kind. But it also exposes a generic “child component” interface, which is the only thing a generic parent component cares about.
What are useful things and what are obnoxious things for a child component to do? Flexibly adapting itself to a range of sizes is useful. Demanding that it should be centered is obnoxious. In fact it would be hard to express at all in most OO widget toolkits. So I think this problem is very closely related to good OO design.
In other words, the view rendered by the component is a function of properties like height and width. But when you throw in margin or align-self, the component now depends on its siblings.
The OO perspective is interesting — I agree that it effectively makes these things hard to express. I now think that it's more of a problem relating to encapsulation and isolation. OO would isolate it through a child component interface while FP would have a function that can't affect siblings.
I think this approach actually results in _less_ useful component isolation in that you can no longer reason about layout of the component _itself_ in isolation, because it can be much more difficult, sometimes impossible, to predict how the styles in the component itself will interact with the styles passed through by the parent.
Instead, I'd recommend always using plain block elements with no explicit height/width/flex properties for the outermost bounding box of your reusable components, so parents can _indirectly_ control how they're laid out using flexbox/grid and add containers that constrain the available space for them to render in and add flex properties if necessary.
Totally agree with the suggestion of using spacing components over margin though. We called this the "golden rule of components" in that reusable components should be entirely responsible for rendering everything within its outermost bounding box (outside of delegating certain parts/properties of it through explicit APIs), and should render nothing outside of it (including empty space, i.e. margins). Of course, edge cases exist where it's necessary to break away from this guideline, but I feel it's a great general rule of thumb for creating flexible reusable components.
Could collapsing margins be an exception to this rule, in principle?
I’ve mostly worked in Android, iOS and React Native, none of which have collapsing margins. But I’ve often wished they had, as it would make some layouts a lot simpler! It would be great to just smoosh components together and have them agree between themselves how much space is needed.
HTML has collapsing margins but they seem kind of half-baked. Is there enough functionality there to be useful, or is it best avoided entirely?
Generally I'd recommend avoiding them entirely, as the rules for when margins should collapse are nuanced and sometimes applied inconsistently between browsers.
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Mod...
It just adds too much cognitive overhead to reasoning about your layouts to be worthwhile imo. Whereas with spacing components, what you see in the virtual dom is what you'll get.
E.g. you have to make an element "display: inline-flex" to make it an inline flexbox. You can't just make it "flex" and have the parent decide whether it is inline, a block or whatever.
The consequence of this is that a parent component cannot properly layout a child component without knowing its internal layout structure. And if it doesn't don't know the child's layout (if its dynamically supplied for example) then it can't safely do anything with it. So long as this limitation exists, there will never be true layout encapsulation on the web platform.
For a while, the CSS Working Group planned to address this problem through the introduction of separate `display-inside` and `display-outside` properties. This would mean an element could specify its internal layout using `display-inside` while its parent could control its external layout using `display-outside`. Then, in their wisdom, they dropped it from the spec[1].
I mean, it's not like developing with encapsulated components is a popular approach on the web nowadays. It's not like many people are using React, or Angular, or Vue, or Web Components—the W3C's own goddamn multi-year effort to push component-based development—so why bother wasting time adding features that would help?
https://developer.mozilla.org/en-US/docs/Web/CSS/display https://developer.mozilla.org/en-US/docs/Web/CSS/display-ins...
I've found this hook (https://github.com/react-spring/react-use-measure) to be reasonably robust, as long as you only use the size properties and don't rely on the accuracy of the positioning properties, which is not something that ResizeObserver is designed to handle (it can get out of date when the component moves due to dynamic content appearing/disappearing around it without actually resizing: https://github.com/react-spring/react-use-measure/issues/9).
It remains to be seen how this could scale when applied to larger "container" type components and the entire app though, as I imagine cascading re-renders as higher level elements resize could become an issue. Curious if people have experience with using ResizeObserver for component size queries at scale?
https://css-tricks.com/auto-sizing-columns-css-grid-auto-fil...
Here's a working link:
You can use 'contain' CSS properties to guarantee that layout inside the element won't affect anything outside it and vice versa. 'contain: strict' is basically a bulletproof container for arbitrary content, with performance benefits too. I'm not sure if it covers everything the article was trying to achieve, but it seems surprising not to mention it.
Abstracting that out makes it easier to achieve a commonly used layout. Not quite sure the best way to implement this, but I'll definitely be giving the idea a shot.
https://seek-oss.github.io/braid-design-system/components/St...
https://github.com/seek-oss/braid-design-system/blob/master/...
I'm honestly still surprised that this isn't the default. Beyond maintainability, it's an obvious security problem, which clickjacking made apparent years ago.
The security implications of user interface API apparently isn't well understood outside the capability security community though:
Design of the EROS Trusted Window System, http://srl.cs.jhu.edu/courses/600.439/shap04windowsystem.pdf
> Remember when we were building our apps as a set of screens and pages instead of thinking in components?
If pages were components in the browser, there wouldn't be any difference. Xanadu had something like this way back in the early 90s, ie. page transclusion.