diff --git a/site/src/components/Section/Action.tsx b/site/src/components/Section/Action.tsx
new file mode 100644
index 0000000000..8a40c90fb9
--- /dev/null
+++ b/site/src/components/Section/Action.tsx
@@ -0,0 +1,17 @@
+import { makeStyles } from "@material-ui/core/styles"
+import React from "react"
+
+const useStyles = makeStyles((theme) => ({
+ root: {
+ marginTop: theme.spacing(3),
+ },
+}))
+
+/**
+ * SectionAction is a content box that call to actions should be placed
+ * within
+ */
+export const SectionAction: React.FC = ({ children }) => {
+ const styles = useStyles()
+ return
{children}
+}
diff --git a/site/src/components/Section/SectionView.stories.tsx b/site/src/components/Section/SectionView.stories.tsx
new file mode 100644
index 0000000000..13f5a1486a
--- /dev/null
+++ b/site/src/components/Section/SectionView.stories.tsx
@@ -0,0 +1,37 @@
+import Button from "@material-ui/core/Button"
+import TextField from "@material-ui/core/TextField"
+import { Story } from "@storybook/react"
+import React from "react"
+import { Section, SectionProps } from "./"
+
+export default {
+ title: "Page/Section",
+ component: Section,
+ argTypes: {
+ title: { type: "string" },
+ description: { type: "string" },
+ children: { control: { disable: true } },
+ },
+}
+
+const Template: Story = (args: SectionProps) =>
+
+export const Example = Template.bind({})
+Example.args = {
+ title: "User Settings",
+ description: "Add your personal info",
+ children: (
+ <>
+
+
+
+
+
+ >
+ ),
+}
diff --git a/site/src/components/Section/index.tsx b/site/src/components/Section/index.tsx
new file mode 100644
index 0000000000..e51ad892df
--- /dev/null
+++ b/site/src/components/Section/index.tsx
@@ -0,0 +1,73 @@
+import { makeStyles } from "@material-ui/core/styles"
+import { fade } from "@material-ui/core/styles/colorManipulator"
+import Typography from "@material-ui/core/Typography"
+import React from "react"
+import { SectionAction } from "./Action"
+
+type SectionLayout = "fixed" | "fluid"
+
+export interface SectionProps {
+ title?: React.ReactNode | string
+ description?: React.ReactNode
+ toolbar?: React.ReactNode
+ alert?: React.ReactNode
+ layout?: SectionLayout
+ children?: React.ReactNode
+}
+
+type SectionFC = React.FC & { Action: typeof SectionAction }
+
+export const Section: SectionFC = ({ title, description, toolbar, alert, children, layout = "fixed" }) => {
+ const styles = useStyles({ layout })
+ return (
+
+
+ {(title || description) && (
+
+
+ {title &&
{title}}
+ {description && typeof description === "string" && (
+
{description}
+ )}
+ {description && typeof description !== "string" && (
+
{description}
+ )}
+
+ {toolbar &&
{toolbar}
}
+
+ )}
+ {alert &&
{alert}
}
+ {children}
+
+
+ )
+}
+
+// Sub-components
+Section.Action = SectionAction
+
+const useStyles = makeStyles((theme) => ({
+ root: {
+ backgroundColor: theme.palette.background.paper,
+ boxShadow: `0px 18px 12px 6px ${fade(theme.palette.common.black, 0.02)}`,
+ marginBottom: theme.spacing(1),
+ padding: theme.spacing(6),
+ },
+ inner: ({ layout }: { layout: SectionLayout }) => ({
+ maxWidth: layout === "fluid" ? "100%" : 500,
+ }),
+ alert: {
+ marginBottom: theme.spacing(1),
+ },
+ header: {
+ marginBottom: theme.spacing(4),
+ display: "flex",
+ flexDirection: "row",
+ justifyContent: "space-between",
+ },
+ description: {
+ color: theme.palette.text.secondary,
+ fontSize: 16,
+ marginTop: theme.spacing(2),
+ },
+}))