12 ms·
Layout-Isolated Components
- fabb64 7y ago@media queries based on browser width are especially bad. Unfortunately there is no css way for stylings dependent on the width of the _component_ itself.
- baddox 7y agoFocusing on window-based instead of component-based responsive queries was a mistake (or at least, it has overstayed its welcome). CSS Grid actually has some component-based responsiveness with auto-fit/auto-fill and minmax, although it's fairly limited. https://css-tricks.com/auto-sizing-columns-css-grid-auto-fill-vs-auto-fit/ https://css-tricks.com/auto-sizing-columns-css-grid-auto-fil...
- lewisl9029 7y agoIt's not CSS based, but I've had reasonable success with ResizeObserver based component size queries, at least in the context of small components that don't accept other nodes to render. I've found this hook (https://github.com/react-spring/react-use-measure 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 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?
- tenaciousDaniel 7y agoIt isn't entirely true that padding is safe in terms of layout isolation.
- emilsjolander 7y agoCorrect :) But it generally is and I didn't wan't to go all too deep
- Izkata 7y agoNot much need to go in a lot of depth, box-sizing:border-box simply makes it so.
- aero142 7y agoSo, the article talks about what not to do. Are there good examples of layout isolated components inside a complex UI?
- AshleysBrain 7y agoWe make a browser-based game development IDE (www.construct.net), and IMO an important aspect of this is CSS containment: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Containment https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Contain... 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.
- jbverschoor 7y agoSo.... non-responsive?
- iainmerrick 7y agoOr, “Object-oriented” 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.
- kbr 7y agoHow does it become any more object-oriented? 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.
- iainmerrick 7y agoMargins and align-self aren’t side-effects. Their semantics are consistent and reproducible and depend only on the context a component is placed in, just as a property like width does. 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.
- kbr 7y agoTheir semantics might be consistent, but they sort of leak into the parent. Height and width are local to what the component renders, and they don't depend on the parent (as long as they aren't relative sizes). But things like margin and align-self change how the parent renders all of its children. 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.
- lewisl9029 7y agoWhile I agree with all of the problem statements in this article, I don't quite agree with the proposed solution of just exposing style props for parents to pass through arbitrary values, at least not as a general solution (it could still be the appropriate solution for certain special cases). 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.
- iainmerrick 7y agoreusable components [...] should render nothing outside of it (including empty space, i.e. margins). 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?
- lewisl9029 7y ago> 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_Model/Mastering_margin_collapsing 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.
- naasking 7y ago> Layout-isolated component - A component that is unaffected by the parent it is placed within, and does not itself affect the size and position of its siblings. 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 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.
- stupidcar 7y agoA fundamental problem of building isolated components in HTML/CSS is that the CSS `display` property sets both an element's inner layout (how it lays out its children) and its outer layout (how it is laid inside it parent). 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? [1] https://drafts.csswg.org/css-display/#changes-wd https://drafts.csswg.org/css-display/#changes-wd
- chrisweekly 7y agoChild selectors (including eg "adjacent siblings") can solve the hardest part of this problem. Some layouts require a wrapper element, but that is just a minor inconvenience, doesn't have to be a blocker. See https://every-layout.dev https://every-layout.dev
- spankalee 7y agoYour vitriol towards CSSWG is misplaced. The CSSWG didn't remove the functionality of `display-inside` and `display-outside`, they just made `display` multi-valued so you can write `display: inline grid`. https://developer.mozilla.org/en-US/docs/Web/CSS/display https://developer.mozilla.org/en-US/docs/Web/CSS/display https://developer.mozilla.org/en-US/docs/Web/CSS/display-inside https://developer.mozilla.org/en-US/docs/Web/CSS/display-ins...
- chrisweekly 7y agoIt's crucial to account properly for relationships between components. The best approach to this I've encountered in over 20 years of web-related experience is found at https://every-layout.dev https://every-layout.dev. Compose complex, responsive layouts from expertly crafted primitives, using axiomatic CSS, and positioning becomes a breeze. The non-layout-related styling can be handled however you like; my preference is emotion for component-scoped styles (`sx` prop and styledcomponent API FTW), and design tokens for colors and typographic scale. I've been meaning to write up my approach bc it's exciting to be freed (and free others) from fighting endless css battles.
- tln 7y agoI hope you do write it up! Here's a working link: https://every-layout.dev/ https://every-layout.dev/
- chrisweekly 7y agoThanks! (fixed link)
- spankalee 7y agoThe usefulness of this articled is lessened because it's React-specific and React is bad with DOM and style isolation in general. 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.
- deleted 7y ago[deleted]
- TomMarius 7y agoconst 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!
- spankalee 7y agoThat's not stylable from CSS though, so you have to pass JS props and can't use selectors. It also duplicates styling strings across all similar style attributes which is not good for memory or perf.
- andrewrothman 7y agoI really like the idea proposed here of <Spacer /> components. I always thought it was weird to assign a margin to elements of a list to achieve spacing. To maintain proper spacing it's often best to either make the top and bottom margins of each element equal, or remove the top/bottom margin from the first/last element. Both techniques come with complications. 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/Stack/ https://seek-oss.github.io/braid-design-system/components/St... https://github.com/seek-oss/braid-design-system/blob/master/lib/components/Stack/Stack.tsx https://github.com/seek-oss/braid-design-system/blob/master/...