Theme UI
If rolling-your-own components hasn't got you hooked, you may be interested in a pre-built option.
Migrating from an earlier version of raam?
Prior to version 1, raam exported a set of individual Theme-UI components. These have now moved to into a single <Flex /> component with variants:
<Stack {...options} />=><Flex variant="vStack" {...options} /><Inline {...options} />=><Flex variant="hStack" sx={{ overflowX: "auto" }} {...options} /><Wrap {...options} />=><Flex variant="wrap" {...options} />
Installation
npm i --save theme-ui @raam/theme-ui
or
yarn add theme-ui @raam/theme-ui
Flexbox
A flexbox-based layout primitive that aims to address the gap.
// import { Flexbox } from "@raam/theme-ui";// import { Box } from "theme-ui";<Flexbox alignItems={["center", "start", "end"]} variant="hStack" gap={[3, 4, 5]}> {Array.from({ length: 6 }).map((item, index) => ( <FlexboxItem key={index}> <Box sx={{ width: "2rem", height: `${index + 1}rem`, backgroundColor: "primary", filter: index > 0 && `brightness(${100 - index * 10}%)`, }} /> </FlexboxItem> ))}</Flexbox>Props
Props for each
flexbox()option.gapalso accepts a key fromtheme.space(as a string or number), but if that's not found it'll render the provided string (e.g.emorrem) or number as apxvalue.
as: change the HTML element rendered (via Theme UI)raam makes an opinionated choice on how to render a component's children based on the element provided:
aschildrenrenderedasdiv(default)divolli(withlist-style-typereset)ulli(withlist-style-typereset)spanspanpspanh1-h6spansx: apply themed styles to the component (via Theme UI).
Recipes
Box
Each recipe assumes you have a Box component defined for base styles. In this case, an sx prop is used to pass themed style values (like Theme UI).
You don't necessarily need to follow this approach, feel free to make it your own.
Wrap
A flex-based layout that renders and 'wraps' children inline, spaced
by the defined gap.
// import { flexbox } from "raam";// import { Box } from "./box";() => { const { parent, child } = flexbox({ variant: "wrap" }); return ( <Box as="ul" sx={parent()}> {Array.from({ length: 32 }).map((item, index) => ( <Box as="li" key={index} sx={{ ...child({ index }), width: "2rem", height: "2rem", backgroundColor: "primary", filter: index > 0 && `brightness(${100 - index * 2}%)`, }} /> ))} </Box> );};Customisation
Let's make it more interesting, let's say we want the last item to fill the remaining available space.
When the index indicates the "last child" (the array's length - 1),
we'll pass the additional flexGrow parameter to the .child and set it to 2.
With the gap prop, we also have the ability to create responsive
styles. In this example, we'll reduce the gap size on larger screens.
// import { flexbox } from "raam";// import { Box } from "./box";() => { const { parent, child } = flexbox({ variant: "wrap", gap: { initial: "2rem", "@media (min-width: 40em)": "1rem", }, }); return ( <Box as="ul" sx={parent()}> {Array.from({ length: 32 }).map((item, index, arr) => ( <Box as="li" key={index} sx={{ ...child({ index, flexGrow: arr.length - 1 === index && 2, }), width: "2rem", height: "2rem", backgroundColor: "primary", filter: index > 0 && `brightness(${100 - index * 2}%)`, }} /> ))} </Box> );};Stack
Popularised by Seek's "Braid", the "Stack" is a flex-based layout that renders children in a single column or row, spaced by the defined gap.
VStack
Renders items in a single column.
// import { flexbox } from "raam";// import { Box } from "./box";() => { const { parent, child } = flexbox({ variant: "vStack", gap: "1rem" }); return ( <Box sx={parent()}> {Array.from({ length: 4 }).map((item, index) => ( <Box key={index} sx={{ ...child({ index }), height: "2rem", backgroundColor: "primary", }} /> ))} </Box> );};Hold up, why don't you just…"
- "…use
display: grid;" Grid is fantastic for page layouts, but has its caveats for a 'simple'Stack:- It doesn't behave reliably for all elements (e.g.
fieldset) - Can lead to 'blow out'.
- It doesn't behave reliably for all elements (e.g.
HStack
The default setting of flexbox; the HStack renders items in a single row and makes no assumptions on your overflow.
// import { flexbox } from "raam";// import { Box } from "./box";() => { const { parent, child } = flexbox({ variant: "hStack", gap: "1rem" }); return ( <Box sx={parent()}> {Array.from({ length: 8 }).map((item, index) => ( <Box key={index} sx={{ ...child({ index }), width: "2rem", height: "2rem", backgroundColor: "primary", }} /> ))} </Box> );};Inline
If you'd rather let hStack items scroll elegantly in a single line, add an overflowX declaration alongside your .child() styles.
// import { flexbox } from "raam";// import { Box } from "./box";() => { const { parent, child } = flexbox({ variant: "hStack", gap: "1rem" }); return ( <Box sx={{ ...parent(), overflowX: "auto" }}> {Array.from({ length: 32 }).map((item, index) => ( <Box key={index} sx={{ ...child({ index }), width: "2rem", height: "2rem", backgroundColor: "primary", filter: index > 0 && `brightness(${100 - index * 2}%)`, }} /> ))} </Box> );};or with some more chaotic values…
// import { flexbox } from "raam";// import { Box } from "./box";() => { const size = () => `${Math.floor(Math.random() * 4) + 1}rem`; const { parent, child } = flexbox({ variant: "hStack", gap: "1rem", alignItems: "center", }); return ( <Box sx={{ ...parent(), overflowX: "auto" }}> {Array.from({ length: 32 }).map((item, index) => ( <Box key={index} sx={{ ...child({ index }), width: size(), height: size(), backgroundColor: "primary", filter: index > 0 && `brightness(${100 - index * 2}%)`, }} /> ))} </Box> );};Combo
When combining flexbox() styles, split them across different elements to avoid conflicts.
Let's take a look at a more real-world example; a "tag"-list at the bottom of an article:
- Padding is added to the
Stack, not theWrapdirectly.
// import { flexbox } from "raam";// import { Box } from "./box";() => { const { parent: stackParent, child: stackChild } = flexbox({ variant: "vStack", gap: "1rem", }); const { parent: wrapParent, child: wrapChild } = flexbox({ variant: "wrap", gap: "1rem", }); return ( <Box sx={{ ...stackParent(), padding: 3 }}> <Heading as="h2" sx={stackChild({ index: 0 })}> Tags </Heading> <Box sx={stackChild({ index: 1 })}> <Box as="ul" sx={wrapParent()}> {Array.from({ length: 8 }, (v, k) => k + 1).map((item, index) => ( <Box key={index} as="li" sx={wrapChild({ index })}> <Link href={`#list-item-${item}`}>Tag {item}</Link> </Box> ))} </Box> </Box> </Box> );};