Closed kettanaito closed 4 years ago
I have recently refactored my website from Rebass Flex
-based layouts to atomic-layout
and then to styled-system
. Why did I do it? I'm new to CSS grid, but I have heard good things about it. And I've been following Kettanaito for a while now, and he has been working on atomic-layout
for about a year. I always wanted to try building something with atomic-layout
, but the opportunity did not present itself until I decided to rebuild my website.
Overall, I'm glad I started my CSS grid journey from atomic-layout
. This library has great docs powered by Gitbook, another project I admire and have actually contributed to. The docs start with the motivation section, where Kettanaito outlines the core principals of composition and responsiveness in atomic-layout
. And that makes sense, but in retrospective, I think the #1 motivation was different for me.
CSS grid helps me to avoid adding margins all over the place. Just think about it, if you are a frontend developer, how often do you wonder if you should add a bottom margin to this component, or a top margin to the next one. What about the last component in the list, will it have a margin too? Or do you need to do the index !== array.length - 1 && ...
dance.
My absolutely favorite thing about atomic-layout
is that it does not render components that are not in the current template. When I say "current template", I refer to the responsive nature of atomic-layout
and how you can have different templates for different viewport sizes. This is great. On my website I 3 main sections: my bio, my external links, and my pinned GitHub projects. All three sections fit very nicely onto a desktop size screen. However, on mobile, I decided to separate them into individual pages and separate layouts. With atomic-layout
I can reuse the children
function that renders all the components but uses different templates for different mobile pages that show subsets of those components.
Now, regarding things that I don't quite like about atomic-layout
. The number one would probably be the children
function that Composition
takes, which in turn takes an object of grid-area
wrappers. I see why the author has done it this way because render-props are cool and this why you don't have to worry about grid-area
. But I found myself in constant need to come up with names for these wrappers. And I know I'm not alone in that naming things is difficult.
Another thing that could be better about atomic-layout
is the API. It seems too new for me. And I don't like that it does not map to the actual CSS property names. I'm sure every frontend developer who has worked with Bootstrap-type grids can figure out what a gutter
is, but the CSS grid term for that is grid-gap
and I think the CSS Working Group would like us to adopt the new terminology moving forward.
When I was integrating atomic-layout
onto my website I browsed the GitHub page of the project quite a lot. And found this issue, which I found curious. I'm a big fan of styled-system
. My website is built with it. And funny enough at first I was upset that atomic-layout
does not automatically integrate with my breakpoints from styled-system
. I never thought of styled-system
as a competitor for atomic-layout
. Mainly because I use Rebass
and it does not have a grid component, so I assumed that the grid is out of styled-systems
's domain, but turns out it's not. It turns out I can implement my own grid component, very similar to atomic-layout
, in just about 30 lines of code:
import styled from 'styled-components';
import {
gridAutoColumns,
gridAutoRows,
gridGap,
gridTemplateAreas,
gridTemplateRows,
gridTemplateColumns,
space,
gridArea,
display
} from 'styled-system';
const Grid = styled.div`
display: grid;
${gridTemplateAreas}
${gridGap}
${gridAutoRows}
${gridAutoColumns}
${gridTemplateRows}
${gridTemplateColumns}
${space}
${gridArea}
${display}
`;
export default Grid;
If you are not familiar with styled-system
, what we have here is a bunch of "mixins" if you will, that expose certain react props relevant to the CSS grid APIs. Note that I added space
for padding only, because who needs margins anymore? And display
is for hiding elements that are not in the current template. This part I'm not quite happy about, but this is the best I could think of for now.
- const repoTemplate = `
- emoji title
- emoji description
- emoji links
-`;
The new Grid
requires templates in a slightly different format: the double quotes are required. But on the flip side, the template is no longer space sensitive and can be inlined into props.
- <Composition template={repoTemplate} gutter={theme.space[4]} {...props}>
- {({Emoji, Title, Description, Links}) => (
- <>
- <Emoji as={Repo.Emoji}>
- {repo.description.substr(0, 2)}
- </Emoji>
+ <Grid
+ gridTemplateAreas={`
+ "emoji title"
+ "emoji description"
+ "emoji links"
+ `}
+ gridGap={4}
+ {...props}
+ >
+ <Emoji gridArea="emoji">
+ {repo.description.substr(0, 2)}
+ </Emoji>
As you can see here, that API is a little different, but not too much. The template
became gridTemplateAreas
, the gutter
turned into gridGap
. And there are no more wrapper components so I need to add gridArea
from styled-system
as you can see below, but on the other hand, I don't need to namespace the Emoji
component anymore.
- Repo.Emoji = styled.p`
+ const Emoji = styled.p`
+ ${gridArea}
And to give you another example, here is a dynamic grid of repos you can see on my website.
- {({Repos, More, TotalStars, Bio}) => (
- <Repos>
- <Composition
- autoRows
- areas="area"
- areasLg="area area"
- areasXl="area area area"
- gutter={theme.space[4]}
- gutterLg={theme.space[5]}
- >
- {() => pinnedRepoTrail.map(({x, ...rest}, index) => {
+ <Grid
+ gridAutoRows
+ gridArea="repos"
+ gridTemplateAreas={[
+ '"area"',
+ '"area"',
+ '"area area"',
+ '"area area area"'
+ ]}
+ gridGap={{sm: 4, lg: 5}}
+ >
+ {pinnedRepoTrail.map(({x, ...rest}, index) => {
This concludes my comparison. I think both approaches are very worthy, and the main difference between them is naming conventions that the two libraries adopt. Personally, I'm going to stick with styled-system
, because its naming feels more standardized to me. And I like how all styled-system
props are universally responsive.
Overall I'm very happy with the CSS grid, and I hope to use it more in the future.
my-website my-website-repos css-grid-changes-everything kettanaito styled-system-issue
Hi, @Hermanya. I absolutely love your comparison and I was reading it with so much thrill! The way you highlight similarities and differences is constructive and practical, love it. Thank you!
Atomic layout has a way to go, a bunch of API to refine and introduce. I'm glad to see some pain points that I've experienced myself, and to know that we have proposals to solve them. To be frank, I think that the comparison with more mature solution is too early to make, but it's handy to have since it's a common question people ask. I will add my comparison once I gain more experience with styled-system
.
I will briefly go through some of your points to keep this thread updated about things that are likely to change in the future releases of atomic-layout
.
And funny enough at first I was upset that
atomic-layout
does not automatically integrate with my breakpoints fromstyled-system
.
This is crucial. There are two steps to perform to address this:
styled-system
and atomic-layout
correspondigly. I don't quite share the array-ish way of providing viewport-based values, as I find it non-intuitive and an being too library-specific.Another thing that could be better about atomic-layout is the API.
That's true, some prop aliases used in atomic-layout
may not be the best. I think #126 should fix that problem, allowing to 1) specify your own aliases, 2) refactor existing ones, making them more spec-compliant.
But I found myself in constant need to come up with names for these wrappers. (context: usage of render prop in Composition returns named area components that may collide with your components).
Try Template-less composition. It's not required to always use render prop with Composition
. So you can omit namespace collision by render children as-is:
<Composition
templateCols="250px 1fr"
templateRows="100px 200px"
gutter={20}
>
<header />
<content />
<footer />
</Composition>
Children of composition still abide by the CSS Grid you create around them. Theoretically, you can also use areas
and provide component-ares mapping by yourself, instead of using a render prop. I need to mention, however, there is more to the render prop than just areas mapping. It also determines conditional areas and automatically wraps them in media query placeholders for you. So manual replacement for render prop would not be equal in behavior.
I've opened an issue for namespace collision (#154), so everybody is welcome to contribute. It would be great to resolve this, as I can feel the pain around that.
Overall, I want to make atomic-layout
combinable with styled-system
, or any other solution. I don't see a reason not to do that, as its primary concern is spacial distribution. Please feel free to tell me your ideas on Discord, or via GitHub issues. Your feedback is welcome, as always :)
Both styled-system
and atomic-layout
have a Responsive props API. In this post I'd like to compare them, and provide more reasons and practical examples of both usage. I will also try to explain why the existing API for atomic-layout
was chosen and what pros and cons it has.
I will be comparing
atomic-layout
responsive props API with the same API instyled-system
package using a list of values. It is also possible to use Objects for responsive props instyled-system
, which solves all of the issues below, but in my opinion introduces unnecessary depth of prop value assignment. That way usingatomic-layout
API is shorter and feels like usual props, which is more native to React ecosystem.
styled-system | atomic-layout | |
---|---|---|
How responsive values are kept? | Array of values | Individual breakpoint-value pairs |
styled-system
uses Array to keep responsive values. This creates a property-based way to assign values, also making the declaration more concise:
<Box padding={[10, 20, 30]} />
However, I find this not very intuitive as a newcomer to that library. It feels like an extra library-specific thing I need to learn to use it. It also raised a few questions the first time I saw it: what is the association between breakpoints and values? How to skip/autoplace certain values? Those can be answered, but I need to reach to a documentation each time I doubt.
atomic-layout
wants to have a more intuitive API, where you can understand what a responsive prop does without reaching to the documentation, or having doubts.
<Box padding={10} paddingMd={20} paddingLg={30} />
On the other hand, such declaration is indeed more lengthy. This can be dealt with by using snippets and shortcuts. The bottom line is to have a proper balance between predictability and shortness.
styled-system |
atomic-layout |
|
---|---|---|
Does the order of values matter? | Yes | No* |
Since styled-system
keeps responsive values in a list, it makes the order of values matter (just as any list does). This may seem okay or even good at first, but that's only when you think about homogenous breakpoints (such as those affecting only min/max-width). However, that's not the only way breakpoints can be used. You can have aspect ratios, screen resolutions, and device types, which make very useful breakpoints. Placing those values before, in between, or after any other breakpoints makes no semantic sense. You would want your arrays to represent similar data type most of the time.
atomic-layout
declares responsive values as individual breakpoint-value pairs. Still the same API decision, which also makes values declaration non-dependant on other values, including order. The order of responsive values you give doesn't matter, because you give them directly in props (which is an Object), where order of properties never matters.
<Box paddingLg={30} padding={10} paddingMd={20} />
- According to #189, the order of responsive props in
atomic-layout
does matter. You may consider this point as equal, or irrelevant in the scope of this comparison. Breakpoint-value pairs still allow you to define connection between different breakpoints and their values without breaking an expectations from an array (because there is no array).
Because of this, you can use various breakpoints side-by-side without appearing awkward in both declaration and semantics:
<Box padding={10} paddingRetina={20} paddingScreenReader={30} />
Based from the previous point, I wonder how much effort it takes to introduce new or deprecate an existing breakpoint in styled-system
? Since the order matters, it most likely means to go and adjust each usage point upon any major change. And since it's property-based, you are left to search all usage of arrays in your app, which may not be very guaranteeing.
// let's say we had two breakpoints
<Box padding={[10, 20]} />
<AnotherBox={[10, 20]} />
// but now we want to add a third one in between:
<Box pading={[10, 15, 20]} /> // we need to cover it here
<AnotherBox={[10, 15, 20]} /> // and here
// and if we accidentally screw something, a wrong value would be applied
// on the introduced breakpoint. That's rarely what we want.
atomic-layout
is meant to help create layouts that are easy to maintain, which means easy to add and deprecate things. Yet again, atomic-layout
uses individual breakpoint-value pairs as a choice for responsive props API. Since each responsive value is a separate pair, there are no dependency between other pairs. This makes adding and deprecating breakpoints easy, as you don't have to adjust the values of non-related responsive props.
// let's say we have two breakpoints
<Box padding={10} paddingLg={20} />
<AnotherBox padding={10} paddingLg={20} />
// and now we want to have a new one in between
<Box padding={10} paddingMd={15} paddingLg={20} />
<AnotherBox padding={10} paddingMd={15} paddingLg={20} />
// adding a value for a new breakpoint is *explicit*
// we don't have to touch existing implementation, meaning less breaking factor.
// and we need to add new values for a new breakpoint explicitly,
// meaning nothing would happen with the existing implementation unless
// we explicitly tell to.
@kettanaito the styled system doc mentions object api for responsive props. https://github.com/styled-system/styled-system/blob/master/docs/responsive-styles.md#using-objects
That being said, last time I tried to use them, I could not get them to work. But from design perspective, I kinds like this approach.
@Hermanya, yes, I've mentioned that at the beginning of my post (in the quote). With object as a value styled-system is still prop-based, while atomic-layout is pair-based. Apart from that the difference would be the depth of objects nesting:
// styled-system
<Box padding={{ large: 10 }} /> // 2 levels: props, then value of "padding"
// atomic-layout
<Box paddingLg={10} /> // 1 level: Box's props
I have explicitly compared an array usage, since it looks like the most popular way people use responsive props in styled-system. Also the most common question I get is to explain why I don't copy such API.
Thanks, I missed that.
@kettanaito i had a look on styled-system library, the idea differ, atomic-layout simply consider spacing as a component itself which is <composition/>
by removing sapcing context completely from component context( decoupling spacing and component context ), so i dont think its comparable that much, styled-system
can be used together with atomic-layout
:)
@ruhaise, true. The intention is different, but certain aspects of two libraries cross over. For example, both solutions suggest an API to handle responsive props. That may raise a decision for you which one to use in the end, and may potentially lead to unpleasant situations when you try to utilize two APIs at once (which you shouldn't do).
I've tried to cover the difference and reasoning behind Responsive props API in this thread for more technical insights. It would be awesome to make Styled System and Atomic Layout compatible on-demand (i.e. via explicit options).
Posted the outlines of this discussion to the docs: Comparison with styled-system. Huge thanks to all for participating. Please feel free to comment on this issue with your own experience and vision of difference.
Edit: I plan to simplify the comparison page by showcasing the same UI built with two different solutions in the future. If you have a desire to contribute, comment here. Thanks.
What:
Need to add a comparison between Atomic layout and
styled-system
.Why:
Two solutions seem similar at first glance, and it's fair to let developers know the difference between them, to pick the right one for their needs.
How: