diff --git a/.changeset/brave-singers-knock.md b/.changeset/brave-singers-knock.md
new file mode 100644
index 00000000000..7a6da135c91
--- /dev/null
+++ b/.changeset/brave-singers-knock.md
@@ -0,0 +1,19 @@
+---
+'braid-design-system': minor
+---
+
+---
+new:
+ - PageBlock
+---
+
+**PageBlock:** Add new component
+
+Provides a top-level page container, constraining the content width (using `ContentBlock`) while establishing common screen gutters on smaller devices.
+
+**EXAMPLE USAGE:**
+```jsx
+
+ ...
+
+```
diff --git a/packages/braid-design-system/src/lib/components/ContentBlock/ContentBlock.docs.tsx b/packages/braid-design-system/src/lib/components/ContentBlock/ContentBlock.docs.tsx
index b9fe8d651dc..362ead61449 100644
--- a/packages/braid-design-system/src/lib/components/ContentBlock/ContentBlock.docs.tsx
+++ b/packages/braid-design-system/src/lib/components/ContentBlock/ContentBlock.docs.tsx
@@ -21,7 +21,9 @@ const docs: ComponentDocs = {
the content it wraps.
),
- alternatives: [],
+ alternatives: [
+ { name: 'PageBlock', description: 'For page-level layout blocks' },
+ ],
additional: [
{
label: 'Maximum width',
diff --git a/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.docs.tsx b/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.docs.tsx
new file mode 100644
index 00000000000..1052ebbeb6c
--- /dev/null
+++ b/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.docs.tsx
@@ -0,0 +1,104 @@
+import React, { Fragment } from 'react';
+import type { ComponentDocs } from 'site/types';
+import { Placeholder } from '../private/Placeholder/Placeholder';
+import { Box, PageBlock, TextLink } from '../';
+import source from '../../utils/source.macro';
+import { Strong } from '../Strong/Strong';
+import { Text } from '../Text/Text';
+import { gutters, validPageBlockComponents } from './PageBlock';
+
+const docs: ComponentDocs = {
+ category: 'Layout',
+ migrationGuide: true,
+ Example: () =>
+ source(
+
+
+ ,
+ ),
+ description: (
+
+ Provides a top-level page container, constraining the content width (using{' '}
+ ContentBlock) while
+ establishing common screen gutters on smaller devices.
+
+ ),
+ alternatives: [
+ {
+ name: 'ContentBlock',
+ description: 'For controlled width layout blocks',
+ },
+ ],
+ additional: [
+ {
+ label: 'Maximum width',
+ description: (
+
+ Use the width prop to adjust the maximum width of the
+ page container. Choose from either medium or{' '}
+ large.
+
+ ),
+ Example: () =>
+ source(
+
+
+ ,
+ ),
+ },
+ {
+ label: 'Screen gutters',
+ description: (
+ <>
+
+ Establishes consistent responsive gutters between the content and
+ the screen edge.
+
+
+ Uses {gutters.mobile} space on{' '}
+ mobile and the semantic{' '}
+ {gutters.tablet} on{' '}
+ tablet and above.
+
+ >
+ ),
+ playroom: false,
+ code: false,
+ Example: () =>
+ source(
+
+
+
+
+
+
+ ,
+ ),
+ },
+
+ {
+ label: 'Custom semantics',
+ description: (
+
+ The HTML tag can be customised to ensure the underlying document
+ semantics are meaningful. This can be done using the{' '}
+ component prop and supports{' '}
+ {validPageBlockComponents.map((c, i) => {
+ const notLastTwo = validPageBlockComponents.length - 2;
+ const joiningLastElements = i === notLastTwo ? ' and ' : '.';
+
+ return (
+
+ {c}
+ {c === 'div' ? ' (default)' : ''}
+ {i < notLastTwo ? ', ' : joiningLastElements}
+
+ );
+ })}
+
+ ),
+ },
+ ],
+};
+
+export default docs;
diff --git a/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.gallery.tsx b/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.gallery.tsx
new file mode 100644
index 00000000000..f6791947a5c
--- /dev/null
+++ b/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.gallery.tsx
@@ -0,0 +1,26 @@
+import React from 'react';
+import type { ComponentExample } from 'site/types';
+import { Placeholder } from '../private/Placeholder/Placeholder';
+import { PageBlock } from '../';
+import source from '../../utils/source.macro';
+
+export const galleryItems: ComponentExample[] = [
+ {
+ label: 'Medium width',
+ Example: () =>
+ source(
+
+
+ ,
+ ),
+ },
+ {
+ label: 'Large width',
+ Example: () =>
+ source(
+
+
+ ,
+ ),
+ },
+];
diff --git a/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.screenshots.tsx b/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.screenshots.tsx
new file mode 100644
index 00000000000..d0e8c7585e8
--- /dev/null
+++ b/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.screenshots.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import type { ComponentScreenshot } from 'site/types';
+import { Placeholder } from '../private/Placeholder/Placeholder';
+import { PageBlock } from '../';
+
+export const screenshots: ComponentScreenshot = {
+ screenshotWidths: [320, 1200],
+ examples: [
+ {
+ label: 'Default',
+ Example: () => (
+
+
+
+ ),
+ },
+ {
+ label: 'Medium',
+ Example: () => (
+
+
+
+ ),
+ },
+ {
+ label: 'Large',
+ Example: () => (
+
+
+
+ ),
+ },
+ ],
+};
diff --git a/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.snippets.tsx b/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.snippets.tsx
new file mode 100644
index 00000000000..81b9ea3ce43
--- /dev/null
+++ b/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.snippets.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import type { Snippets } from '../private/Snippets';
+import { PageBlock, Placeholder } from '../../playroom/components';
+import source from '../../utils/source.macro';
+
+export const snippets: Snippets = [
+ {
+ name: 'Medium',
+ code: source(
+
+
+ ,
+ ),
+ },
+ {
+ name: 'Large',
+ code: source(
+
+
+ ,
+ ),
+ },
+];
diff --git a/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.tsx b/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.tsx
new file mode 100644
index 00000000000..5589c5ec3ab
--- /dev/null
+++ b/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.tsx
@@ -0,0 +1,50 @@
+import React, { type ReactNode } from 'react';
+import {
+ ContentBlock,
+ type ContentBlockProps,
+} from '../ContentBlock/ContentBlock';
+import { Box } from '../Box/Box';
+import buildDataAttributes, {
+ type DataAttributeMap,
+} from '../private/buildDataAttributes';
+
+export const validPageBlockComponents = [
+ 'div',
+ 'article',
+ 'aside',
+ 'main',
+ 'section',
+ 'nav',
+] as const;
+
+export const gutters = { mobile: 'xsmall', tablet: 'gutter' } as const;
+
+interface Props {
+ children: ReactNode;
+ width?: Extract;
+ component?: (typeof validPageBlockComponents)[number];
+ data?: DataAttributeMap;
+}
+
+export const PageBlock = ({
+ children,
+ width = 'large',
+ component: componentProp,
+ data,
+ ...restProps
+}: Props) => {
+ const component =
+ componentProp && validPageBlockComponents.includes(componentProp)
+ ? componentProp
+ : 'div';
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/packages/braid-design-system/src/lib/components/index.ts b/packages/braid-design-system/src/lib/components/index.ts
index cf52a4a581b..ca94951b350 100644
--- a/packages/braid-design-system/src/lib/components/index.ts
+++ b/packages/braid-design-system/src/lib/components/index.ts
@@ -56,6 +56,7 @@ export { MenuItemLink } from './MenuItem/MenuItemLink';
export { OverflowMenu } from './OverflowMenu/OverflowMenu';
export { MonthPicker } from './MonthPicker/MonthPicker';
export { Notice } from './Notice/Notice';
+export { PageBlock } from './PageBlock/PageBlock';
export { Pagination } from './Pagination/Pagination';
export { PasswordField } from './PasswordField/PasswordField';
export { Radio } from './Radio/Radio';
diff --git a/packages/braid-design-system/src/lib/playroom/snippets.ts b/packages/braid-design-system/src/lib/playroom/snippets.ts
index 717f6e788b8..dcd7bda0d3b 100644
--- a/packages/braid-design-system/src/lib/playroom/snippets.ts
+++ b/packages/braid-design-system/src/lib/playroom/snippets.ts
@@ -24,6 +24,7 @@ import { snippets as Loader } from './snippets/Loader';
import { snippets as MonthPicker } from './snippets/MonthPicker';
import { snippets as Notice } from './snippets/Notice';
import { snippets as OverflowMenu } from './snippets/OverflowMenu';
+import { snippets as PageBlock } from './snippets/PageBlock';
import { snippets as Pagination } from './snippets/Pagination';
import { snippets as PasswordField } from './snippets/PasswordField';
import { snippets as RadioGroup } from './snippets/RadioGroup';
@@ -70,6 +71,7 @@ export default Object.entries({
MonthPicker,
Notice,
OverflowMenu,
+ PageBlock,
Pagination,
PasswordField,
RadioGroup,
diff --git a/packages/generate-component-docs/src/__snapshots__/contract.test.ts.snap b/packages/generate-component-docs/src/__snapshots__/contract.test.ts.snap
index 5737c9d59c9..4196660c389 100644
--- a/packages/generate-component-docs/src/__snapshots__/contract.test.ts.snap
+++ b/packages/generate-component-docs/src/__snapshots__/contract.test.ts.snap
@@ -6797,6 +6797,26 @@ exports[`OverflowMenu 1`] = `
}
`;
+exports[`PageBlock 1`] = `
+{
+ exportType: component,
+ props: {
+ children: ReactNode
+ component?:
+ | "article"
+ | "aside"
+ | "div"
+ | "main"
+ | "nav"
+ | "section"
+ data?: DataAttributeMap
+ width?:
+ | "large"
+ | "medium"
+},
+}
+`;
+
exports[`Pagination 1`] = `
{
exportType: component,
diff --git a/site/src/App/DocNavigation/DocExample.tsx b/site/src/App/DocNavigation/DocExample.tsx
index 938ddb34af2..40584b6b01a 100644
--- a/site/src/App/DocNavigation/DocExample.tsx
+++ b/site/src/App/DocNavigation/DocExample.tsx
@@ -41,7 +41,7 @@ export const DocExample = ({
{value}
) : null}
- {codeAsString ? (
+ {code !== false && codeAsString ? (
{codeAsString}
diff --git a/site/src/App/routes/foundations/layout/layout.tsx b/site/src/App/routes/foundations/layout/layout.tsx
index ee0a9a8d95e..e3cd6360f3f 100644
--- a/site/src/App/routes/foundations/layout/layout.tsx
+++ b/site/src/App/routes/foundations/layout/layout.tsx
@@ -21,6 +21,7 @@ import {
Hidden,
Strong,
Bleed,
+ PageBlock,
} from 'braid-src/lib/components';
import { TextStack } from '../../../TextStack/TextStack';
import Code from '../../../Code/Code';
@@ -85,6 +86,9 @@ const page: Page = {
ContentBlock
+
+ PageBlock
+
Bleed
@@ -773,6 +777,41 @@ const page: Page = {
+ PageBlock
+
+ For top-level sections, in addition to limiting the width of content on
+ the screen, it is also important to standardise the gutter between
+ content and the edge of the screen. For this Braid provides the{' '}
+ PageBlock component,
+ which defines responsive gutters around a{' '}
+ ContentBlock.
+
+
+ {source(
+
+
+ Hello World
+
+ ,
+ )}
+
+
+ To standardise our page-level block widths, the{' '}
+ width{' '}
+ prop accepts either medium or large.
+
+
+ {source(
+
+
+ Hello World
+
+ ,
+ )}
+
+
+
+
Bleed
Sometimes it is necessary for a component to extend out into it’s
diff --git a/site/src/types.d.ts b/site/src/types.d.ts
index 80c0112bb52..325928bc66a 100644
--- a/site/src/types.d.ts
+++ b/site/src/types.d.ts
@@ -62,7 +62,7 @@ export interface ComponentExample {
props: ExampleProps & PlayroomExampleProps,
) => Source;
Container?: (props: { children: ReactNode }) => ReactElement;
- code?: string;
+ code?: string | false;
showCodeByDefault?: boolean;
playroom?: boolean;
}