Layout Components

Layout components are an abstraction on top of CSS.

Made popular by Mark Dalgleish's talk https://www.youtube.com/watch?v=jnV1u67_yVg (opens in a new tab) on Braid's design-system https://seek-oss.github.io/braid-design-system/components/Stack/ (opens in a new tab).

Article on the subject by styled-component's creator https://mxstbr.com/thoughts/margin (opens in a new tab)

At LesFurets, we were able to build a complete app without a single line of css. CSS was only used in design-system components, which included a Box and a a Stack component for layout.

Pros:

Cons:

Box

An abstraction that represents space around a component. (Our UX designers use the word "spacing" so we went with it, but it's just margins)

<Box horizontalSpacing="4px">
  <Child/>
</Box>
 
<Box spacingTop="2px" spacingLeft="10px">
  <Child/>
</Box>

Those "2px" declarations can be replaced with consts or a TS enum

Stack

An abstraction that stacks components in a line or a pile.

Could use /*prettier-ignore*/ for a visual code representation:

;<Stack column>
  <Child1 />
  <Child2 />
  <Child3 />
</Stack>
;<Stack line gap="2px">
  <Child1 />
  <Child2 />
  <Child3 />
</Stack>

We could use /*prettier-ignore*/ for a visual code representation where "Row" childrens are in the same line.

Making the bundler translate indentation to line/column might be an idea to explore...

Aiming for dev Flow

At Koober, we went for a shorter API, we used concise naming and minimized effort in typing/auto-importing/completing.
We reached flow state more often that way.
Switching files, browsing imports, bringing up auto-completion, live-reload delays etc. are all tiny details that can break flow.

API Example:

<Row m={2} bg="red.100">
  <Box px={4}>Hello</Box>
  <Column ml={4} gap={2}>
    <Box>World 1</Box>
    <Box>World 2</Box>
  </Column>
</Row>

Using boolean props can help making the API even more concise but it can cause confusion.

<Stack column />
<Stack column row>Can be confusing</Stack>

Responsive in JS

import { useState, useEffect } from 'react'
 
export function useMediaQuery(query) {
  const [matches, setMatches] = useState(false)
 
  useEffect(() => {
    const media = window.matchMedia(query)
    if (media.matches !== matches) {
      setMatches(media.matches)
    }
    const listener = () => {
      setMatches(media.matches)
    }
    media.addListener(listener)
    return () => media.removeListener(listener)
  }, [matches, query])
 
  return matches
}
 
function Page() {
  let isPageWide = useMediaQuery('(min-width: 800px)')
 
  return (
    <>
      {isPageWide && <UnnecessarySidebar />}
      <ImportantContent />
    </>
  )
}

Credits: https://www.netlify.com/blog/2020/12/05/building-a-custom-react-media-query-hook-for-more-responsive-apps/ (opens in a new tab)

We abstracted on top of this to match UX designer's language and their breakpoints:

const { isMobile, isTablet, isDesktop } = useResponsive()
CC BY-NC 4.0 2024 © Shu Ding.