Skip to content
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0b15af6
Update MinimalFooter gridline layout
danielguillan Jul 16, 2026
9dc7331
Update changeset
danielguillan Jul 29, 2026
0aa15e2
Update MinimalFooter
danielguillan Jul 30, 2026
d0d612f
Fixes and cleanu
danielguillan Jul 30, 2026
5b77d1b
Update changeset
danielguillan Jul 30, 2026
4ff8383
Update docs
danielguillan Jul 30, 2026
f765dc0
Update tests
danielguillan Jul 30, 2026
2a84390
Cleanup
danielguillan Jul 30, 2026
23b3d7e
Rename footer regions into sections
danielguillan Jul 30, 2026
f768376
Layout fixes
danielguillan Jul 30, 2026
eb2660c
Merge main into MinimalFooter updates
danielguillan Jul 30, 2026
1dc3bfc
Update snapshots
danielguillan Jul 30, 2026
2e326fb
Clean up
danielguillan Jul 30, 2026
c700408
Update stories
danielguillan Jul 30, 2026
556a9da
Fix back to top control styles
danielguillan Jul 30, 2026
4f4edd0
Fix inline padding on large viewports
danielguillan Jul 30, 2026
7753e6d
Update snapshots
danielguillan Jul 30, 2026
75cdb82
Fix typo in docs
danielguillan Jul 31, 2026
b6afa65
Cleanup
danielguillan Jul 31, 2026
1ca1d22
Fix alignment
danielguillan Jul 31, 2026
336b41a
Fix docs
danielguillan Jul 31, 2026
6481cfb
Update snapshots
danielguillan Jul 31, 2026
a25fef9
Merge branch 'danielguillan/brand-refactor-minimal-footer-updates' of…
danielguillan Jul 31, 2026
5d65c12
Address feedback
danielguillan Aug 4, 2026
a05ac07
Update snapshots
danielguillan Aug 4, 2026
650d791
Address feedback
danielguillan Aug 6, 2026
9be256c
Auto-merged main into danielguillan/brand-refactor-minimal-footer-upd…
danielguillan Aug 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/bright-dots-rest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@primer/brand-primitives': minor
'@primer/react-brand': minor
---

Updated `MinimalFooter` visual design and added a center component prop and back to top control.
Comment thread
danielguillan marked this conversation as resolved.

- Added the optional `centerComponent` prop for exceptional page-specific content and the optional `MinimalFooter.BackToTop` control.
- Replaced the full GitHub logo with the GitHub logomark and reorganized the footer into top, center, and bottom sections.
70 changes: 57 additions & 13 deletions apps/next-docs/content/components/MinimalFooter/index.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Minimal footer
description: Use the minimal footer component to provide a global footer featuring legal links, GitHub logomarks and footnotes.
description: Use the minimal footer component to provide a global footer featuring legal links, social links, and optional footnotes.
keywords: ['legal', 'copyright', 'social', 'links', 'footnotes']
show-tabs: false
ready: true
Expand All @@ -27,6 +27,8 @@ import {MinimalFooter} from '@primer/react-brand'

### Footer links

Use up to five `MinimalFooter.Link` children. Links can render as anchors or buttons.

```jsx live
<MinimalFooter>
<MinimalFooter.Link href="https://github.com/organizations/enterprise_plan">Try GitHub for free</MinimalFooter.Link>
Expand Down Expand Up @@ -61,7 +63,7 @@ import {MinimalFooter} from '@primer/react-brand'

### Footer social links

`MinimalFooter` will render all supported social links by default. Social links can be removed however, by passing `socialLinks={false}`. Alteratively, pass an array of valid social links to filter the default list.
`MinimalFooter` will render all supported social links by default. Social links can be removed however, by passing `socialLinks={false}`. Alternatively, pass an array of valid social links to filter and reorder the default list.

```jsx live
<Stack>
Expand All @@ -73,27 +75,69 @@ import {MinimalFooter} from '@primer/react-brand'
</Stack>
```

### Center component

Use `centerComponent` for an exceptional page-specific control or status indicator between the top and bottom sections.

```jsx live
<MinimalFooter
centerComponent={
<Stack direction="horizontal" gap="condensed" padding="none">
<Text size="200" variant="muted">
Status:
</Text>
<InlineLink href="https://www.githubstatus.com" size="200">
All systems operational
</InlineLink>
</Stack>
}
/>
```

### Back to top

Use `MinimalFooter.BackToTop` to opt into a control in the top row that scrolls the document back to the top.

After keyboard activation, focus moves to the first `<main>` element, falling back to `<body>` or `<html>` when a main landmark is unavailable. Use `focusTargetId` to identify a different semantic top-of-page destination.

```jsx live
<MinimalFooter socialLinks={false}>
<MinimalFooter.BackToTop>Back to top</MinimalFooter.BackToTop>
</MinimalFooter>
```

## Component props

### MinimalFooter

| Name | Type | Default | Description |
| :------------------- | :----------------------------------------------------------------------------------------------- | :-----------------------------------------------------------------------------------: | :----------------------------------------------------- |
| `className` | `string` | | Sets a custom class on the root element |
| `children` | <MinimalFooterChildrenProp /> | `undefined` | Valid child components |
| `copyrightStatement` | `string` \| `ReactElement` | | The copyright statement to be displayed in the footer. |
| `logoHref` | `string` | `'https://github.com'` | The href for the GitHub logo. |
| `socialLinks` | `['x', 'github', 'linkedin', 'youtube', 'facebook', 'twitch', 'tiktok', 'instagram']` \| `false` | `['x', 'github', 'linkedin', 'youtube', 'facebook', 'twitch', 'tiktok', 'instagram']` | Array of valid strings or `false` to hide all links |
| Name | Type | Default | Description |
| :------------------- | :------------------------------------------------------------------------------------------------------------------ | :-----------------------------------------------------------------------------------: | :----------------------------------------------------------------- |
| `className` | `string` | | Sets a custom class on the root element |
| `centerComponent` | `ReactElement` | `undefined` | Renders an optional component between the top and bottom sections. |
| `children` | <MinimalFooterChildrenProp /> | `undefined` | Valid child components |
| `copyrightStatement` | `string` \| `ReactElement` | Current-year GitHub copyright statement | The copyright statement displayed in the bottom section. |
| `logoHref` | `string` | `'https://github.com'` | The href for the GitHub logomark. |
| `socialLinks` | `Array<'x' \| 'github' \| 'linkedin' \| 'youtube' \| 'facebook' \| 'twitch' \| 'tiktok' \| 'instagram'>` \| `false` | `['x', 'github', 'linkedin', 'youtube', 'facebook', 'twitch', 'tiktok', 'instagram']` | Ordered subset of social links, or `false` to hide all links. |

### MinimalFooter.Link

| Name | Type | Default | Description |
| :--------- | :---------------- | :---------: | :---------------------------------- |
| `as` | `'a'`, `'button'` | `'a'` | Applies the underlying HTML element |
| `children` | `string` | `undefined` | Link label text |
| Name | Type | Default | Description |
| :--------- | :---------------- | :---------: | :------------------------------------- |
| `as` | `'a'`, `'button'` | `'a'` | Applies the underlying HTML element |
| `children` | `string` | `undefined` | Label for a link in the bottom section |

### MinimalFooter.Footnotes

| Name | Type | Default | Description |
| :--------- | :----------- | :---------: | :-------------------------------------------------------------------- |
| `children` | ReactElement | `undefined` | Use Primer Brand `Text` components to benefit from inherited styling. |

### MinimalFooter.BackToTop

| Name | Type | Default | Description |
| :-------------- | :------------------ | :---------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `children` | `ReactNode` | Required | The visible and accessible localized label. |
| `focusTargetId` | `string` | `undefined` | ID of the semantic top-of-page element that receives focus after keyboard activation. Defaults to the first `<main>`, then falls back to `<body>` or `<html>`. |
| `onClick` | `MouseEventHandler` | `undefined` | Runs before the built-in behavior. Call `event.preventDefault()` to cancel scrolling and focus transfer. |

All other props, including ARIA and `data-*` attributes, forward to the underlying `<button>`.
5 changes: 4 additions & 1 deletion apps/next-docs/content/components/MinimalFooter/react.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@
import {PropTableValues} from '@primer/doctocat-nextjs/components'

export const MinimalFooterChildrenProp = () => (
<PropTableValues values={['MinimalFooter.Link', 'MinimalFooter.Footnotes']} addLineBreaks />
<PropTableValues
values={['MinimalFooter.Link', 'MinimalFooter.Footnotes', 'MinimalFooter.BackToTop']}
addLineBreaks
/>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"footer": {
"backToTop": {
"minTarget": {
"value": "{base.size.44}"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,50 @@
{
"footer": {
"borderColor": {
"value": "var(--base-color-scale-green-6)",
"dark": "var(--base-color-scale-green-3)"
},
"bg": {
"color": {
"value": "var(--base-color-scale-white)",
"dark": "var(--base-color-scale-gray-9)"
}
},
"backToTop": {
"borderColor": {
"rest": {
"value": "var(--base-color-scale-gray-2)",
"dark": "var(--base-color-scale-gray-6)"
},
"hover": {
"value": "var(--base-color-scale-gray-2)",
"dark": "rgba(255, 255, 255, 0.12)"
},
"active": {
"value": "var(--base-color-scale-gray-2)",
"dark": "rgba(255, 255, 255, 0.12)"
}
},
"bgColor": {
"rest": {
"value": "rgba(0, 0, 0, 0.01)",
"dark": "rgba(0, 0, 0, 0.01)"
},
"hover": {
"value": "rgba(0, 0, 0, 0.1164)",
"dark": "rgba(255, 255, 255, 0.16)"
},
"active": {
"value": "rgba(0, 0, 0, 0.2104)",
"dark": "rgba(255, 255, 255, 0.24)"
}
}
},
"socialIcon": {
"restFilter": {
"value": "sepia(37%) saturate(152%) hue-rotate(77deg) brightness(57%)",
"dark": "saturate(73%) hue-rotate(-89deg) brightness(110%)"
},
"hoverFilter": {
"value": "brightness(0)",
"dark": "brightness(2)"
Expand Down
108 changes: 105 additions & 3 deletions packages/react/src/MinimalFooter/MinimalFooter.features.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import React from 'react'
import type {Meta, StoryObj} from '@storybook/react'
import {CheckCircleFillIcon} from '@primer/octicons-react'
import {expect, within} from 'storybook/test'
import {MinimalFooter} from '.'
import {InlineLink, Text, ThemeProvider} from '..'
import {InlineLink, Text, ThemeProvider, Token} from '..'
import {RedlineBackground} from '../component-helpers'

export default {
title: 'Components/MinimalFooter/Features',
Expand Down Expand Up @@ -92,12 +95,12 @@ export const FilteredSocialLinks: Story = {

export const ReversedSocialLinks: Story = {
render: () => (
<MinimalFooter socialLinks={['x', 'github', 'linkedin', 'youtube', 'facebook', 'twitch', 'tiktok', 'instagram']} />
<MinimalFooter socialLinks={['instagram', 'tiktok', 'twitch', 'facebook', 'youtube', 'linkedin', 'github', 'x']} />
),
}

export const DefaultNarrow: Story = {
name: 'Default (Narrow viewport)',
name: 'Default, narrow view (mobile)',
globals: {
viewport: {value: 'iphonexr'},
},
Expand Down Expand Up @@ -127,6 +130,18 @@ export const DefaultNarrow: Story = {
<MinimalFooter.Link href="https://github.com/enterprise/contact">Email us</MinimalFooter.Link>
</MinimalFooter>
),
play: async ({canvasElement}) => {
const socialLinks = within(canvasElement).getByRole('list')
const bottom = socialLinks.closest('section')
const container = bottom?.firstElementChild

if (!container) {
throw new Error('Expected the social footer layout to render')
}

expect(socialLinks.getBoundingClientRect().right).toBeLessThanOrEqual(container.getBoundingClientRect().right)
expect(document.documentElement.scrollWidth).toBe(document.documentElement.clientWidth)
},
}

export const DarkTheme: Story = {
Expand All @@ -136,3 +151,90 @@ export const DarkTheme: Story = {
</ThemeProvider>
),
}

export const CustomContent: Story = {
render: () => (
<MinimalFooter
centerComponent={
<Token as="a" href="https://www.githubstatus.com" variant="accent" leadingVisual={<CheckCircleFillIcon />}>
All systems operational
</Token>
}
/>
),
}

export const BackToTop: Story = {
render: () => (
<main>
<RedlineBackground style={{height: '120vh'}}>
<Text as="p" align="center" weight="semibold">
Scroll down to test the back-to-top functionality
</Text>
</RedlineBackground>
<MinimalFooter>
<MinimalFooter.BackToTop>Back to top</MinimalFooter.BackToTop>
</MinimalFooter>
</main>
),
}

const ResponsiveExample = ({socialLinks = true}: {socialLinks?: boolean}) => (
<MinimalFooter
centerComponent={
<Token as="a" href="https://www.githubstatus.com" variant="accent" leadingVisual={<CheckCircleFillIcon />}>
All systems operational
</Token>
}
copyrightStatement="GitHub, Inc. © 2026. All rights reserved."
socialLinks={socialLinks ? undefined : false}
>
<MinimalFooter.Footnotes>
<Text>
<sup>1</sup>Additional terms may apply. See the{' '}
<InlineLink href="https://docs.github.com/en/site-policy/privacy-policies/github-privacy-statement">
GitHub Privacy Statement
</InlineLink>
.
</Text>
</MinimalFooter.Footnotes>
<MinimalFooter.BackToTop>Back to top</MinimalFooter.BackToTop>
<MinimalFooter.Link href="#">Sitemap</MinimalFooter.Link>
<MinimalFooter.Link href="#">What is Git?</MinimalFooter.Link>
<MinimalFooter.Link href="#">Manage cookies</MinimalFooter.Link>
<MinimalFooter.Link href="#">Do not share or sell my personal information</MinimalFooter.Link>
<MinimalFooter.Link href="#">Terms of Service</MinimalFooter.Link>
</MinimalFooter>
)

export const NarrowView: Story = {
name: 'Narrow view (mobile)',
globals: {
viewport: {value: 'iphonexr'},
},
render: () => <ResponsiveExample />,
}

export const RegularView: Story = {
name: 'Regular view (tablet)',
globals: {
viewport: {value: 'ipad10p'},
},
render: () => <ResponsiveExample />,
}

export const NarrowViewNoSocialLinks: Story = {
name: 'Narrow view, no social links (mobile)',
globals: {
viewport: {value: 'iphonexr'},
},
render: () => <ResponsiveExample socialLinks={false} />,
}

export const RegularViewNoSocialLinks: Story = {
name: 'Regular view, no social links (tablet)',
globals: {
viewport: {value: 'ipad10p'},
},
render: () => <ResponsiveExample socialLinks={false} />,
}
Loading
Loading