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.gap
also accepts a key fromtheme.space
(as a string or number), but if that's not found it'll render the provided string (e.g.em
orrem
) or number as apx
value.
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:
as
children
renderedas
div
(default)div
ol
li
(withlist-style-type
reset)ul
li
(withlist-style-type
reset)span
span
p
span
h1
-h6
span
sx
: 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 theWrap
directly.
// 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> );};