diff --git a/docs-ui/src/app/hooks/page.mdx b/docs-ui/src/app/hooks/page.mdx
new file mode 100644
index 0000000000..ea73f07ce3
--- /dev/null
+++ b/docs-ui/src/app/hooks/page.mdx
@@ -0,0 +1,7 @@
+import { HookGrid } from '@/components/HookGrid';
+
+# Hooks
+
+Backstage UI custom hooks provide easy access to design system features, style management, and responsive interface creation.
+
+
diff --git a/docs-ui/src/app/hooks/use-breakpoint/components.tsx b/docs-ui/src/app/hooks/use-breakpoint/components.tsx
new file mode 100644
index 0000000000..c58010c66f
--- /dev/null
+++ b/docs-ui/src/app/hooks/use-breakpoint/components.tsx
@@ -0,0 +1,17 @@
+'use client';
+
+import { useBreakpoint } from '@backstage/ui';
+
+export function UseBreakpointExample() {
+ const { breakpoint, up, down } = useBreakpoint();
+
+ return (
+
+
Current Breakpoint: {breakpoint}
+ {(up('md') &&
The viewport is larger than 1024px.
) ||
+ (down('sm') &&
The viewport is smaller than 768px.
) || (
+
The viewport is between 768px and 1024px.
+ )}
+
+ );
+}
diff --git a/docs-ui/src/app/hooks/use-breakpoint/page.mdx b/docs-ui/src/app/hooks/use-breakpoint/page.mdx
new file mode 100644
index 0000000000..f7f3378a41
--- /dev/null
+++ b/docs-ui/src/app/hooks/use-breakpoint/page.mdx
@@ -0,0 +1,47 @@
+import { ChangelogComponent } from '@/components/ChangelogComponent';
+import { PageTitle } from '@/components/PageTitle';
+import { PropsTable } from '@/components/PropsTable';
+import { Snippet } from '@/components/Snippet';
+import { Banner } from '@/components/Banner';
+import { useBreakpointReturnDefs } from './props-definition';
+import { useBreakpointExampleSnippet } from './snippets';
+import { UseBreakpointExample } from './components';
+
+
+
+## Usage
+
+
+
+The `useBreakpoint` hook returns the active breakpoint and two functions, `up` and `down`, to check if the viewport is above, or below a given breakpoint, letting you adjust your UI responsively.
+
+}
+ code={useBreakpointExampleSnippet}
+/>
+
+## Breakpoints
+
+The default breakpoints are:
+
+- **initial**: < 640px
+- **xs**: ≥ 640px < 768px
+- **sm**: ≥ 768px < 1024px
+- **md**: ≥ 1024px < 1280px
+- **lg**: ≥ 1280px < 1536px
+- **xl**: ≥ 1536px
+
+## Return Value
+
+
+
+
diff --git a/docs-ui/src/app/hooks/use-breakpoint/props-definition.ts b/docs-ui/src/app/hooks/use-breakpoint/props-definition.ts
new file mode 100644
index 0000000000..b1702a82cc
--- /dev/null
+++ b/docs-ui/src/app/hooks/use-breakpoint/props-definition.ts
@@ -0,0 +1,23 @@
+import { type PropDef } from '@/utils/propDefs';
+
+const Breakpoint = ['"initial"', '"xs"', '"sm"', '"md"', '"lg"', '"xl"'];
+
+export const useBreakpointReturnDefs: Record = {
+ breakpoint: {
+ type: 'enum',
+ values: Breakpoint,
+ description: 'The current active breakpoint based on screen width',
+ },
+ up: {
+ type: 'enum',
+ values: [`(breakpoint: ${Breakpoint.join(' | ')}) => boolean`],
+ description:
+ 'Function that takes a breakpoint and returns true if the screen width is at or above that breakpoint',
+ },
+ down: {
+ type: 'enum',
+ values: [`(breakpoint: ${Breakpoint.join(' | ')}) => boolean`],
+ description:
+ 'Function that takes a breakpoint and returns true if the screen width is at or below that breakpoint',
+ },
+};
diff --git a/docs-ui/src/app/hooks/use-breakpoint/snippets.ts b/docs-ui/src/app/hooks/use-breakpoint/snippets.ts
new file mode 100644
index 0000000000..19e6debe65
--- /dev/null
+++ b/docs-ui/src/app/hooks/use-breakpoint/snippets.ts
@@ -0,0 +1,13 @@
+export const useBreakpointExampleSnippet = `import { useBreakpoint } from '@backstage/ui';
+
+function ResponsiveComponent() {
+ const { breakpoint, up, down } = useBreakpoint();
+
+ return (
+
+
Current Breakpoint: {breakpoint}
+ {up('md') &&
The viewport is medium or larger.
}
+ {down('sm') &&
The viewport is small or smaller.
}
+
+ );
+}`;
diff --git a/docs-ui/src/components/Banner/styles.module.css b/docs-ui/src/components/Banner/styles.module.css
index 9666c5d521..d9d3323376 100644
--- a/docs-ui/src/components/Banner/styles.module.css
+++ b/docs-ui/src/components/Banner/styles.module.css
@@ -16,13 +16,13 @@
.info {
background-color: #f2f2f2;
border-color: #cdcdcd;
- color: #888888;
+ color: #4b4b4b;
}
.warning {
background-color: #fff2b9;
- border-color: #ffd000;
- color: #d79927;
+ border-color: #d79927;
+ color: #8a3d09;
}
.icon {
diff --git a/docs-ui/src/components/ChangelogComponent/index.tsx b/docs-ui/src/components/ChangelogComponent/index.tsx
index 92068d04f8..cca75a9515 100644
--- a/docs-ui/src/components/ChangelogComponent/index.tsx
+++ b/docs-ui/src/components/ChangelogComponent/index.tsx
@@ -1,16 +1,24 @@
import { changelog } from '@/utils/changelog';
import { MDXRemote } from 'next-mdx-remote-client/rsc';
import { formattedMDXComponents } from '@/mdx-components';
-import type { Component } from '@/utils/changelog';
+import type { AtLeastOne, Component, Hook } from '@/utils/changelog';
import {
Badge,
BreakingBadge,
generateChangelogMarkdown,
} from '../Changelog/utils';
-export const ChangelogComponent = ({ component }: { component: Component }) => {
- const componentChangelog = changelog.filter(c =>
- c.components.includes(component),
+type ChangelogComponentProps = AtLeastOne<{
+ component: Component;
+ hook: Hook;
+}>;
+
+export const ChangelogComponent = ({
+ component,
+ hook,
+}: Readonly) => {
+ const componentChangelog = changelog.filter(
+ c => c.components?.includes(component) || c.hooks?.includes(hook),
);
const content = `## Changelog
diff --git a/docs-ui/src/components/HookGrid/HookGrid.module.css b/docs-ui/src/components/HookGrid/HookGrid.module.css
new file mode 100644
index 0000000000..a6dd54a864
--- /dev/null
+++ b/docs-ui/src/components/HookGrid/HookGrid.module.css
@@ -0,0 +1,29 @@
+.grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ margin-top: 1rem;
+ margin-bottom: 2rem;
+}
+
+@media (min-width: 768px) {
+ .grid {
+ grid-template-columns: repeat(4, 1fr);
+ }
+}
+
+.item {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 10px 0;
+ font-size: 14px;
+ font-weight: 400;
+ color: var(--primary);
+ text-decoration: none;
+
+ &:hover {
+ color: var(--link);
+ text-decoration: underline;
+ text-underline-offset: 2px;
+ }
+}
diff --git a/docs-ui/src/components/HookGrid/HookGrid.tsx b/docs-ui/src/components/HookGrid/HookGrid.tsx
new file mode 100644
index 0000000000..871ee885d2
--- /dev/null
+++ b/docs-ui/src/components/HookGrid/HookGrid.tsx
@@ -0,0 +1,21 @@
+'use client';
+
+import Link from 'next/link';
+import { hooks } from '@/utils/data';
+import styles from './HookGrid.module.css';
+
+export const HookGrid = () => {
+ return (
+
+ {hooks.map(item => (
+
+ {item.title}
+
+ ))}
+
+ );
+};
diff --git a/docs-ui/src/components/HookGrid/index.ts b/docs-ui/src/components/HookGrid/index.ts
new file mode 100644
index 0000000000..934f73aacf
--- /dev/null
+++ b/docs-ui/src/components/HookGrid/index.ts
@@ -0,0 +1 @@
+export * from './HookGrid';
diff --git a/docs-ui/src/components/Navigation/Navigation.tsx b/docs-ui/src/components/Navigation/Navigation.tsx
index bad77e942e..ac62c03d7a 100644
--- a/docs-ui/src/components/Navigation/Navigation.tsx
+++ b/docs-ui/src/components/Navigation/Navigation.tsx
@@ -11,7 +11,7 @@ import {
RiServiceLine,
RiStackLine,
} from '@remixicon/react';
-import { components } from '@/utils/data';
+import { components, hooks } from '@/utils/data';
import styles from './Navigation.module.css';
interface NavigationProps {
@@ -92,6 +92,29 @@ export const Navigation = ({ onLinkClick }: NavigationProps) => {
);
})}
+
+ Hooks
+
+ {hooks.map(item => {
+ const isActive = pathname === `/hooks/${item.slug}`;
+ return (
+
+ {item.title}
+
+ {item.status === 'alpha' && 'Alpha'}
+ {item.status === 'beta' && 'Beta'}
+ {item.status === 'inProgress' && 'In Progress'}
+ {item.status === 'stable' && 'Stable'}
+ {item.status === 'deprecated' && 'Deprecated'}
+
+
+ );
+ })}
>
);
};
diff --git a/docs-ui/src/utils/data.ts b/docs-ui/src/utils/data.ts
index 8132bc952a..753473ab64 100644
--- a/docs-ui/src/utils/data.ts
+++ b/docs-ui/src/utils/data.ts
@@ -138,3 +138,10 @@ export const components: Page[] = [
slug: 'visually-hidden',
},
];
+
+export const hooks: Page[] = [
+ {
+ title: 'useBreakpoint',
+ slug: 'use-breakpoint',
+ },
+];
diff --git a/docs-ui/src/utils/types.ts b/docs-ui/src/utils/types.ts
index 210ff1fc7f..c2c383796f 100644
--- a/docs-ui/src/utils/types.ts
+++ b/docs-ui/src/utils/types.ts
@@ -35,14 +35,22 @@ export type Component =
| 'tooltip'
| 'visually-hidden';
+export type Hook = 'use-breakpoint';
+
export type Version = `${number}.${number}.${number}`;
-export interface ChangelogProps {
- components: Component[];
+export type AtLeastOne = K extends string
+ ? Pick & Partial>
+ : never;
+
+export type ChangelogProps = {
description: string;
version: Version;
prs: string[];
breaking?: boolean;
commitSha?: string;
migration?: string;
-}
+} & AtLeastOne<{
+ components: Component[];
+ hooks: Hook[];
+}>;