Skip to content

Rework full-width layout, add support for full-width page option #3293

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jun 11, 2025
5 changes: 5 additions & 0 deletions .changeset/long-cameras-protect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"gitbook": minor
---

Rework full-width layout, add support for full-width page option
4 changes: 2 additions & 2 deletions packages/gitbook/src/components/DocumentView/Blocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ export function UnwrappedBlocks<TBlock extends DocumentBlock>(props: UnwrappedBl
style={[
'mx-auto w-full decoration-primary/6',
node.data && 'fullWidth' in node.data && node.data.fullWidth
? 'max-w-screen-xl'
: 'max-w-3xl',
? 'max-w-screen-2xl'
: 'page-full-width:ml-0 max-w-3xl',
blockStyle,
]}
isEstimatedOffscreen={isOffscreen}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function Columns(props: BlockProps<DocumentBlockColumns>) {
ancestorBlocks={[...ancestorBlocks, block, columnBlock]}
context={context}
blockStyle="flip-heading-hash"
style="w-full space-y-4"
style="w-full space-y-4 *:max-w-full"
/>
</Column>
);
Expand Down
2 changes: 1 addition & 1 deletion packages/gitbook/src/components/DocumentView/Divider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ import type { BlockProps } from './Block';
export function Divider(props: BlockProps<DocumentBlockDivider>) {
const { style } = props;

return <hr className={tcls(style, 'border-tint-subtle')} />;
return <hr className={tcls(style, 'page-full-width:max-w-full border-tint-subtle')} />;
}
10 changes: 7 additions & 3 deletions packages/gitbook/src/components/Footer/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,16 @@ export function Footer(props: { context: GitBookSiteContext }) {
<div className={tcls(CONTAINER_STYLE, 'px-4', 'py-8', 'lg:py-12', 'mx-auto')}>
<div
className={tcls(
'mx-auto grid max-w-3xl justify-between gap-12 lg:max-w-none',
'lg:!max-w-none mx-auto grid max-w-3xl site-full-width:max-w-screen-2xl justify-between gap-12',
'grid-cols-[auto_auto]',
'lg:grid-cols-[18rem_minmax(auto,_48rem)_auto]',
'xl:grid-cols-[18rem_minmax(auto,_48rem)_14rem]',
'site-full-width:lg:grid-cols-[18rem_minmax(auto,_80rem)_auto]',
'site-full-width:xl:grid-cols-[18rem_minmax(auto,_80rem)_14rem]',
'page-no-toc:lg:grid-cols-[minmax(auto,_48rem)_auto]',
'page-no-toc:xl:grid-cols-[14rem_minmax(auto,_48rem)_14rem]'
'page-no-toc:xl:grid-cols-[14rem_minmax(auto,_48rem)_14rem]',
'[body:has(.site-full-width,.page-no-toc)_&]:lg:grid-cols-[minmax(auto,_90rem)_auto]',
'[body:has(.site-full-width,.page-no-toc)_&]:xl:grid-cols-[14rem_minmax(auto,_90rem)_14rem]'
)}
>
{
Expand Down Expand Up @@ -102,7 +106,7 @@ export function Footer(props: { context: GitBookSiteContext }) {
'col-span-2 page-has-toc:lg:col-span-1 page-has-toc:lg:col-start-2 page-no-toc:xl:col-span-1 page-no-toc:xl:col-start-2'
)}
>
<div className="mx-auto flex max-w-3xl flex-col gap-10 sm:flex-row sm:gap-6">
<div className="mx-auto flex max-w-3xl site-full-width:max-w-screen-2xl flex-col gap-10 sm:flex-row sm:gap-6">
{partition(customization.footer.groups, FOOTER_COLUMNS).map(
(column, columnIndex) => (
<div
Expand Down
5 changes: 1 addition & 4 deletions packages/gitbook/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,9 @@ export function Header(props: { context: GitBookSiteContext; withTopHeader?: boo
'lg:basis-40',
'md:max-w-[40%]',
'lg:max-w-lg',
'lg:ml-[max(calc((100%-18rem-48rem-3rem)/2),1.5rem)]', // container (100%) - sidebar (18rem) - content (48rem) - margin (3rem)
'lg:ml-[max(calc((100%-18rem-48rem)/2),1.5rem)]', // container (100%) - sidebar (18rem) - content (48rem)
'xl:ml-[max(calc((100%-18rem-48rem-14rem-3rem)/2),1.5rem)]', // container (100%) - sidebar (18rem) - content (48rem) - outline (14rem) - margin (3rem)
'page-no-toc:lg:ml-[max(calc((100%-18rem-48rem-18rem-3rem)/2),0rem)]',
'page-full-width:lg:ml-[max(calc((100%-18rem-103rem-3rem)/2),1.5rem)]',
'page-full-width:2xl:ml-[max(calc((100%-18rem-96rem-14rem+3rem)/2),1.5rem)]',
'md:mr-auto',
'order-last',
'md:order-[unset]',
Expand Down Expand Up @@ -195,7 +193,6 @@ export function Header(props: { context: GitBookSiteContext; withTopHeader?: boo
<div
className={tcls(
CONTAINER_STYLE,
'page-default-width:max-w-[unset]',
'grow',
'flex',
'items-end',
Expand Down
4 changes: 1 addition & 3 deletions packages/gitbook/src/components/PageAside/PageAside.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,9 @@ export function PageAside(props: {
'group/aside',
'hidden',
'xl:flex',
// 'page-no-toc:lg:flex',
'flex-col',
'basis-56',
// 'page-no-toc:basis-40',
// 'page-no-toc:xl:basis-56',
'xl:ml-12',
'grow-0',
'shrink-0',
'break-anywhere', // To prevent long words in headings from breaking the layout
Expand Down
16 changes: 7 additions & 9 deletions packages/gitbook/src/components/PageBody/PageBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ export function PageBody(props: {
const { page, context, ancestors, document, withPageFeedback } = props;
const { customization } = context;

const asFullWidth = document ? hasFullWidthBlock(document) : false;
const contentFullWidth = document ? hasFullWidthBlock(document) : false;
const pageFullWidth = page.id === 'wtthNFMqmEQmnt5LKR0q';
const asFullWidth = pageFullWidth || contentFullWidth;
const language = getSpaceLanguage(customization);
const updatedAt = page.updatedAt ?? page.createdAt;

Expand All @@ -36,15 +38,11 @@ export function PageBody(props: {
<main
className={tcls(
'relative min-w-0 flex-1',
'py-8 lg:px-12',
'mx-auto max-w-screen-2xl py-8',
// Allow words to break if they are too long.
'break-anywhere',
// When in api page mode without the aside, we align with the border of the main content
'page-api-block:xl:max-2xl:pr-0',
// Max size to ensure one column in api is aligned with rest of content (2 x 3xl) + (gap-3 + 2) * px-12
'page-api-block:mx-auto page-api-block:max-w-screen-2xl',
// page.layout.tableOfContents ? null : 'xl:ml-56',
asFullWidth ? 'page-full-width' : 'page-default-width',
pageFullWidth ? 'page-full-width 2xl:px-8' : 'page-default-width',
asFullWidth ? 'site-full-width' : 'site-default-width',
page.layout.tableOfContents ? 'page-has-toc' : 'page-no-toc'
)}
>
Expand Down Expand Up @@ -81,7 +79,7 @@ export function PageBody(props: {
<PageFooterNavigation context={context} page={page} />
) : null}

<div className="mx-auto mt-6 page-api-block:ml-0 flex max-w-3xl flex-row flex-wrap items-center gap-4 text-tint contrast-more:text-tint-strong">
<div className="mx-auto mt-6 page-api-block:ml-0 flex max-w-3xl page-full-width:max-w-screen-2xl flex-row flex-wrap items-center gap-4 text-tint contrast-more:text-tint-strong">
{updatedAt ? (
<p className="mr-auto text-sm">
{t(language, 'page_last_modified', <DateRelative value={updatedAt} />)}
Expand Down
14 changes: 10 additions & 4 deletions packages/gitbook/src/components/PageBody/PageCover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,20 @@ export async function PageCover(props: {
? [
'sm:-mx-6',
'md:-mx-8',
'-lg:mr-8',
'lg:ml-0',
'lg:-mr-8',
'lg:-ml-12',
!page.layout.tableOfContents &&
context.customization.header.preset !== 'none'
? 'xl:-ml-64'
? 'xl:-ml-[19rem]'
: null,
]
: ['sm:mx-auto', 'max-w-3xl', 'sm:rounded-md', 'mb-8']
: [
'sm:mx-auto',
'max-w-3xl ',
'page-full-width:max-w-screen-2xl',
'sm:rounded-md',
'mb-8',
]
)}
>
<Image
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ export async function PageFooterNavigation(props: {
'mt-6',
'gap-2',
'max-w-3xl',
'page-full-width:max-w-screen-2xl',
'mx-auto',
'page-api-block:ml-0',
'text-tint'
)}
>
Expand Down
9 changes: 8 additions & 1 deletion packages/gitbook/src/components/PageBody/PageHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,14 @@ export async function PageHeader(props: {

return (
<header
className={tcls('max-w-3xl', 'mx-auto', 'mb-6', 'space-y-3', 'page-api-block:ml-0')}
className={tcls(
'max-w-3xl',
'page-full-width:max-w-screen-2xl',
'mx-auto',
'mb-6',
'space-y-3',
'page-api-block:ml-0'
)}
>
{ancestors.length > 0 && (
<nav>
Expand Down
12 changes: 6 additions & 6 deletions packages/gitbook/src/components/PageBody/PreservePageLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import * as React from 'react';

/**
* This component preserves the layout of the page while loading a new one.
* This approach is needed as page layout (full width block) is done using CSS (`body:has(.page-full-width)`),
* This approach is needed as page layout (full width block) is done using CSS (`body:has(.full-width)`),
* which becomes false while transitioning between the 2 page states:
*
* 1. Page 1 with full width block: `body:has(.page-full-width)` is true
* 2. Loading skeleton while transitioning to page 2: `body:has(.page-full-width)` is false
* 3. Page 2 with full width block: `body:has(.page-full-width)` is true
* 1. Page 1 with full width block: `body:has(.site-full-width)` is true
* 2. Loading skeleton while transitioning to page 2: `body:has(.site-full-width)` is false
* 3. Page 2 with full width block: `body:has(.site-full-width)` is true
*
* This component ensures that the layout is preserved while transitioning between the 2 page states (in step 2).
*/
Expand All @@ -24,9 +24,9 @@ export function PreservePageLayout(props: { asFullWidth: boolean }) {
}

if (asFullWidth) {
header.classList.add('page-full-width');
header.classList.add('site-full-width');
} else {
header.classList.remove('page-full-width');
header.classList.remove('site-full-width');
}
}, [asFullWidth]);

Expand Down
4 changes: 0 additions & 4 deletions packages/gitbook/src/components/RootLayout/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,6 @@
margin-right: 0;
width: calc(100% - var(--scrollbar-width));
}
body:has(.page-full-width) .scroll-nojump {
margin-left: 0;
width: 100%;
}
}

.elevate-link {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function SitePageSkeleton() {
'lg:items-start'
)}
>
<div className={tcls('flex-1', 'max-w-3xl', 'mx-auto', 'page-full-width:mx-0')}>
<div className={tcls('flex-1', 'max-w-3xl', 'mx-auto', 'site-full-width:mx-0')}>
<SkeletonHeading style={tcls('mb-8')} />
<SkeletonParagraph style={tcls('mb-4')} />
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export function SpaceLayout(props: {
'flex-col',
'lg:flex-row',
CONTAINER_STYLE,
'site-full-width:max-w-full',

// Ensure the footer is display below the viewport even if the content is not enough
withFooter && 'min-h-[calc(100vh-64px)]',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export function TableOfContents(props: {
'relative',
'z-[1]',
'lg:sticky',
'lg:mr-12',

// Server-side static positioning
'lg:top-0',
Expand Down
2 changes: 1 addition & 1 deletion packages/gitbook/src/components/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const CONTAINER_STYLE: ClassValue = [
'md:px-8',
'max-w-screen-2xl',
'mx-auto',
'page-full-width:max-w-full',
// 'site-full-width:max-w-full',
];

/**
Expand Down
10 changes: 9 additions & 1 deletion packages/gitbook/tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,13 @@ const config: Config = {
},
},
opacity: opacity(),
screens: {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1536px',
},
},
plugins: [
plugin(({ addVariant }) => {
Expand Down Expand Up @@ -514,8 +521,9 @@ const config: Config = {
/**
* Variant when the page contains a block that will be rendered in full-width mode.
*/
addVariant('site-full-width', 'body:has(.site-full-width) &');
addVariant('site-default-width', 'body:has(.site-default-width) &');
addVariant('page-full-width', 'body:has(.page-full-width) &');
addVariant('page-default-width', 'body:has(.page-default-width) &');

/**
* Variant when the page is configured to hide the table of content.
Expand Down