August 16, 2024 | Derek Watson | Engineering | How To | Web
In Anvil2, we have introduced new components and features to make creating dynamic, responsive page layouts a breeze. Read on to learn more!
Fork this codesandbox to play around with some of the new features! This was used during a tech talk on July 2nd, 2024.
Layout changes from Anvil → Anvil2
Summary
- Enable easy use of Flexbox and CSS Grid
- Two new component – Flex and Grid
- All Anvil2 components have some control over layout using layout utils props
- Simplified APIs
- Flexibility and responsiveness baked-in
Page component updates
See Anvil2 Page Implementation Docs
- Simplified API
- Headers, footers, and action toolbars should now be created independently as part of the layout
- Sidebar and Panel are now sub-components
- Background color can be controlled using CSS (
style or className)
- Better responsive behavior coming soon!
<Page>
<Page.Sidebar>
<Page.SidebarHeader>
<Text variant="headline" el="h2">
Sidebar Header
</Text>
</Page.SidebarHeader>
<SideNav>
<SideNav.Link id="01" href="#">
Nav Link
</SideNav.Link>
</SideNav>
</Page.Sidebar>
<Page.Content>
{/* page content (including the header, action toolbar, footer, etc.) */}
</Page.Content>
</Page>
Layout component updates
See Anvil2 Layout Implementation Docs
- Simplified API with
Layout and Layout.Item used to create columns
- Responsiveness built-in, and customizable
- Three density variants available using
variant prop: "default", "narrow", and "wide"
<Layout variant="narrow">
<Layout.Item span={12}>{/\* full-width content... \*/}</Layout.Item>
<Layout.Item span={3}>{/\* one-quarter-width content... \*/}</Layout.Item>
<Layout.Item span={9}>{/\* three-quarter-width content... \*/}</Layout.Item>
</Layout>
Column structure and responsive behavior
Layouts use a 12-column structure that reduces columns based on the size of the layout. Note that the values below are based on the width of the Layout, not the entire screen (using container queries). This is important if the page includes a sidebar or panel.
- On
sm screens (and below), there are four columns
- On
md screens and up, there are twelve columns
Responsive props can be used to override the span value at certain breakpoints (and higher).
<Layout.Item
// <640px = full-width
span={4}
// 640px -> 767px = 3/4 width
sm={3}
// 768px -> 1023px = 1/3 width
md={4}
// 1024px -> 1279px = 1/2 width
lg={6}
// 1280px -> 1535px = 2/3 width
xl={8}
// >=1536px = 3/4 width
xxl={9}
>
{/*...content */}
</Layout.Item>
New layout features in Anvil2
Flex and Grid components
See the Flex Implementation Docs and Grid Implementation Docs.
Flex is similar to Stack, with some minor differences. A Flex can be used both as a flex container and item, so instead of using Stack.Item, use another Flex or another Anvil2 component. This greatly reduces the amount of code required to build flex layouts!
Anvil (legacy) example
<Stack direction="column" alignItems="center">
<Stack.Item>
<Headline />
</Stack.Item>
<Stack.Item>
<Stack spacing="1">
<Stack.Item fill>
<Text />
</Stack.Item>
<Stack.Item alignSelf="flex-start">
<Button />
</Stack.Item>
</Stack>
</Stack.Item>
</Stack>
Anvil2 example
<Flex direction="column" alignItems="center">
<Text variant="headline" />
<Flex gap="2">
<Text flexGrow="1" />
<Button alignSelf="flex-start" />
</Flex>
</Flex>
The new Grid component can be used to create CSS Grid layouts (not to be confused with the Anvil Grid). Grid components can also be used as grid containers or items, and any Anvil2 component can use special grid props to control its columns and rows.
<Grid
templateColumns="repeat(5, 1fr)"
autoRows="minmax(3rem, auto)"
columnGap="4"
rowGap="2"
>
<Card gridColumn="1 / 6" />
<Card gridRow="2 / 5" />
<Card gridArea="2 / 2 / 5 / 6" />
</Grid>
Responsive props of Flex and Grid
Similar to the Layout.Item, responsive props are also available to override any of the Flex and Grid props at different breakpoints.
<Grid
md={{ templateColumns: "repeat(5, 1fr)", columnGap: "4" }}
autoRows="minmax(3rem, auto)"
rowGap="2"
>
<Card sm={{ gridColumn: "1 / 6" }} />
<Card sm={{ gridRow: "2 / 5" }} />
<Flex
direction="column"
alignItems="center"
md={{ gridArea: "2 / 2 / 5 / 6" }}
>
<Text variant="headline" />
<Flex gap="2">
<Text flexGrow="1" />
<Button alignSelf="flex-start" />
</Flex>
</Flex>
</Grid>
Layout Props
See Layout Props Utilities documentation.
All Anvil2 components other than Page and Layout extend Layout type, which enable them to control various flex and grid properties. props at different breakpoints.
type Layout = {
// flex only
flex?: CSSProperties["flex"];
flexDirection?: CSSProperties["flexDirection"];
flexGrow?: CSSProperties["flexGrow"];
flexShrink?: CSSProperties["flexShrink"];
flexBasis?: CSSProperties["flexBasis"];
// grid only
gridArea?: CSSProperties["gridArea"];
gridColumn?: CSSProperties["gridColumn"];
gridRow?: CSSProperties["gridRow"];
gridColumnStart?: CSSProperties["gridColumnStart"];
gridColumnEnd?: CSSProperties["gridColumnEnd"];
gridRowStart?: CSSProperties["gridRowStart"];
gridRowEnd?: CSSProperties["gridRowEnd"];
// both
alignContent?: CSSProperties["alignContent"];
alignItems?: CSSProperties["alignItems"];
alignSelf?: CSSProperties["alignSelf"];
columnGap?: “0” -> “14” | “half”;
gap?: “0” -> “14” | “half”;
justifyContent?: CSSProperties["justifyContent"];
justifyItems?: CSSProperties["justifyItems"];
justifySelf?: CSSProperties["justifySelf"];
order?: CSSProperties["order"];
placeContent?: CSSProperties["placeContent"];
placeItems?: CSSProperties["placeItems"];
placeSelf?: CSSProperties["placeSelf"];
rowGap?: “0” -> “14” | “half”;
// responsive overrides to all of the props above
sm: Omit<Layout, "sm" | "md" | "lg" | "xl" | "xxl">
md: Omit<Layout, "sm" | "md" | "lg" | "xl" | "xxl">
lg: Omit<Layout, "sm" | "md" | "lg" | "xl" | "xxl">
xl: Omit<Layout, "sm" | "md" | "lg" | "xl" | "xxl">
xxl: Omit<Layout, "sm" | "md" | "lg" | "xl" | "xxl">
}
Several of the props in the Layout type only apply to flex or grid containers. The Card component is a flex container by default. Other components would need display: flex or display: grid added to use the following props:
type LayoutPropsForContainersOnly = {
flexDirection?: CSSProperties["flexDirection"];
alignContent?: CSSProperties["alignContent"];
alignItems?: CSSProperties["alignItems"];
columnGap?: “0” -> “14” | “half”;
gap?: “0” -> “14” | “half”;
justifyContent?: CSSProperties["justifyContent"];
justifyItems?: CSSProperties["justifyItems"];
placeContent?: CSSProperties["placeContent"];
placeItems?: CSSProperties["placeItems"];
rowGap?: “0” -> “14” | “half”;
}
Putting it all together
These components and props work together to make creating flexible, responsive layouts a breeze in Anvil2!
const ExamplePage = () => {
const [panelIsOpen, setPanelIsOpen] = useState(false);
const gridData = [
/*... some objects ...*/
];
return (
<Page>
<Page.Sidebar>
<Page.SidebarHeader>
<Text variant="headline" el="h2">
Sidebar Header
</Text>
</Page.SidebarHeader>
<SideNav>
<SideNav.Item id="1" active>
First Page
</SideNav.Item>
</SideNav>
</Page.Sidebar>
<Page.Panel open={panelIsOpen}>{/* panel content */}</Page.Panel>
<Page.Content>
<Layout fluid>
<Layout.Item span={12}>
<Flex direction="column" gap="2" md={{ direction: "row" }}>
<Text variant="headline" el="h1" md={{ flexGrow: "1" }}>
Page Title
</Text>
<Card direction="column" md={{ alignSelf: "flex-start" }}>
<Text variant="eyebrow">Card header</Text>
<Text>Card content</Text>
</Card>
</Flex>
</Layout.Item>
<Layout.Item span={4} md={5} lg={4}>
{/* small column content */}
{/* xs/sm = full-width */}
{/* md = 5/12 width */}
{/* lg/xl/xxl = 1/3 width */}
</Layout.Item>
<Layout.Item span={4} md={7} lg={8}>
{/* large column content */}
{/* xs/sm = full-width */}
{/* md = 7/12 width */}
{/* lg/xl/xxl = 2/3 width */}
<Grid
gap="2"
sm={{ templateColumns: "repeat(2, 1fr)" }}
md={{ templateColumns: "repeat(3, 1fr)" }}
xl={{ templateColumns: "repeat(4, 1fr)" }}
>
{gridData.map((item) => (
<Card>{/* render gridData item */}</Card>
))}
</Grid>
</Layout.Item>
</Layout>
</Page.Content>
</Page>
);
};
Last modified on January 23, 2026