Vertical & Horizontal Stacks Components with React & FlexBox

November 1, 2022

2 min read

Vertical & Horizontal Stacks Components with React & FlexBox
Watch on YouTube

Most web layout is a combination of grids and stacks of elements that we create with either CSS grid or flexbox. In this post, I want to share how to write significantly less CSS by leveraging Stack components.

import styled, { css } from "styled-components"
import { getCSSUnit } from "lib/ui/utils/getCSSUnit"

interface Props {
  gap?: React.CSSProperties["gap"]
  alignItems?: React.CSSProperties["alignItems"]
  justifyContent?: React.CSSProperties["justifyContent"]
  wrap?: React.CSSProperties["flexWrap"]
  children: React.ReactNode
  fullWidth?: boolean
  fullHeight?: boolean
}

const formatFlexAlignment = (
  value:
    | React.CSSProperties["alignItems"]
    | React.CSSProperties["justifyContent"]
) => {
  if (value === "end" || value === "start") {
    return `flex-${value}`
  }

  return value
}

const stackCSS = css<Props>`
  display: flex;
  ${({ gap }) =>
    gap &&
    css`
      gap: ${getCSSUnit(gap)};
    `}
  ${({ alignItems }) =>
    alignItems &&
    css`
      align-items: ${formatFlexAlignment(alignItems)};
    `}
  ${({ justifyContent }) =>
    justifyContent &&
    css`
      justify-content: ${formatFlexAlignment(justifyContent)};
    `}
  ${({ wrap }) =>
    wrap &&
    css`
      flex-wrap: ${wrap};
    `}
  ${({ fullWidth }) =>
    fullWidth &&
    css`
      width: 100%;
    `}
  ${({ fullHeight }) =>
    fullHeight &&
    css`
      height: 100%;
    `}
`

export const VStack = styled.div`
  ${stackCSS}
  flex-direction: column;
`

export const HStack = styled.div`
  ${stackCSS}
  flex-direction: row;
`

interface StackProps extends Props {
  direction: React.CSSProperties["direction"]
}

export const Stack = styled.div<StackProps>`
  ${stackCSS}
  flex-direction: ${({ direction }) => direction};
`

Here we have three components: VStack, HStack, and Stack. While the first two have fixed directions, the Stack component receives a direction as a property. It could be helpful for responsive views, but I rarely use it since flex-wrap is enough in most cases.

Both vertical and horizontal stacks have shared styles except flex-direction property. These components have only optional properties and don't rewrite flexbox attributes unless there is a prop for it. To set types for gap, alignItems, justify-content, and wrap, we leverage React'sCSSProperties. Often we want to make the stack take full width or height. Safaris don't support end and start values yet, so we have a formatFlexAlignment helper that will add a flex prefix to them.

In my product at increaser.org, I use these components all the time. VStack has 443 occurrences, and HStack 201.