kettanaito / atomic-layout

Build declarative, responsive layouts in React using CSS Grid.
https://redd.gitbook.io/atomic-layout
MIT License
1.13k stars 33 forks source link

Add comparison with styled-system #144

Closed kettanaito closed 4 years ago

kettanaito commented 5 years ago

It is not quite fair to compare these two tools as they are different. I'd like to have this section to list the differences and help people understand when to use which, or both. The outcome of this thread will be evolved into a separate page in the documentation.

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:

  1. Descrive difference.
  2. Include code examples, where applicable.
Hermanya commented 5 years ago

Atomic-layout vs styled-system

Atomic-layout

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.

Styled-system

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) => {

Summary

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

kettanaito commented 5 years ago

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.

Shared responsive behavior

And funny enough at first I was upset that atomic-layout does not automatically integrate with my breakpoints from styled-system.

This is crucial. There are two steps to perform to address this:

  1. Unify breakpoints usage throughout an entire application (#132, #148)
  2. Define breakpoints and provide them to 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.

API, prop names, and conventions

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.

Template-less composition

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 :)

kettanaito commented 5 years ago

Responsive props

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 in styled-system package using a list of values. It is also possible to use Objects for responsive props in styled-system, which solves all of the issues below, but in my opinion introduces unnecessary depth of prop value assignment. That way using atomic-layout API is shorter and feels like usual props, which is more native to React ecosystem.

Array of values

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.

Making breakpoints order matter

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} />

Adding/deprecating breakpoints

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.
Hermanya commented 5 years ago

@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.

kettanaito commented 5 years ago

@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.

Hermanya commented 5 years ago

Thanks, I missed that.

ruhaise commented 5 years ago

@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 :)

kettanaito commented 5 years ago

@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).

kettanaito commented 4 years ago

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.