diff --git a/docs-ui/src/app/hooks/[slug]/page.tsx b/docs-ui/src/app/hooks/[slug]/page.tsx
new file mode 100644
index 0000000000..4f750a917d
--- /dev/null
+++ b/docs-ui/src/app/hooks/[slug]/page.tsx
@@ -0,0 +1,23 @@
+import { hooks } from '@/utils/data';
+
+export default async function Page({
+ params,
+}: {
+ params: Promise<{ slug: string }>;
+}) {
+ const { slug } = await params;
+
+ const { default: Component } = await import(`@/content/hooks/${slug}.mdx`);
+
+ return ;
+}
+
+export function generateStaticParams() {
+ const list = [...hooks];
+
+ return list.map(hook => ({
+ slug: hook.slug,
+ }));
+}
+
+export const dynamicParams = false;
diff --git a/docs-ui/src/app/hooks/page.mdx b/docs-ui/src/app/hooks/page.mdx
new file mode 100644
index 0000000000..b2e2b81e7d
--- /dev/null
+++ b/docs-ui/src/app/hooks/page.mdx
@@ -0,0 +1,34 @@
+import { CodeBlock } from '@/components/CodeBlock';
+import { ComponentCard, ComponentCards } from '@/components/ComponentCards';
+
+# Hooks
+
+Backstage UI custom hooks provide easy access to design system features, style management, and responsive interface creation.
+
+
+
+
+
+
+
+
diff --git a/docs-ui/src/components/Navigation/Navigation.tsx b/docs-ui/src/components/Navigation/Navigation.tsx
index bad77e942e..f4fc51993f 100644
--- a/docs-ui/src/components/Navigation/Navigation.tsx
+++ b/docs-ui/src/components/Navigation/Navigation.tsx
@@ -11,13 +11,26 @@ 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 {
onLinkClick?: () => void;
}
+const data = [
+ {
+ title: 'Components',
+ content: components,
+ url: '/components',
+ },
+ {
+ title: 'Hooks',
+ content: hooks,
+ url: '/hooks',
+ },
+];
+
export const Navigation = ({ onLinkClick }: NavigationProps) => {
const pathname = usePathname();
diff --git a/docs-ui/src/content/hooks/use-breakpoint.example.tsx b/docs-ui/src/content/hooks/use-breakpoint.example.tsx
new file mode 100644
index 0000000000..d4d21861bb
--- /dev/null
+++ b/docs-ui/src/content/hooks/use-breakpoint.example.tsx
@@ -0,0 +1,28 @@
+'use client';
+
+import { useBreakpoint } from '@backstage/ui';
+import { useEffect, useState } from 'react';
+
+export function UseBreakpointExample() {
+ const { breakpoint, up, down } = useBreakpoint();
+ const [isMounted, setIsMounted] = useState(false);
+
+ // prevent hydration mismatch by rendering only on the client
+ useEffect(() => {
+ setIsMounted(true);
+ }, []);
+
+ if (!isMounted) {
+ return null;
+ }
+
+ 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/content/hooks/use-breakpoint.mdx b/docs-ui/src/content/hooks/use-breakpoint.mdx
new file mode 100644
index 0000000000..89f5a7f757
--- /dev/null
+++ b/docs-ui/src/content/hooks/use-breakpoint.mdx
@@ -0,0 +1,44 @@
+import { ChangelogComponent } from '@/components/ChangelogComponent';
+import { CodeBlock } from '@/components/CodeBlock';
+import { PageTitle } from '@/components/PageTitle';
+import { PropsTable } from '@/components/PropsTable';
+import { Snippet } from '@/components/Snippet';
+import { UseBreakpointExample } from './use-breakpoint.example';
+import {
+ useBreakpointExampleSnippet,
+ useBreakpointReturnDefs,
+} from './use-breakpoint.props';
+
+
+
+## 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/content/hooks/use-breakpoint.props.ts b/docs-ui/src/content/hooks/use-breakpoint.props.ts
new file mode 100644
index 0000000000..298b31b3e3
--- /dev/null
+++ b/docs-ui/src/content/hooks/use-breakpoint.props.ts
@@ -0,0 +1,37 @@
+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',
+ },
+};
+
+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/content/hooks/use-media-query.example.tsx b/docs-ui/src/content/hooks/use-media-query.example.tsx
new file mode 100644
index 0000000000..0d8edd944b
--- /dev/null
+++ b/docs-ui/src/content/hooks/use-media-query.example.tsx
@@ -0,0 +1,42 @@
+'use client';
+import { useMediaQuery } from '@backstage/ui/src/hooks/useMediaQuery';
+
+export function UseMediaQueryThemeExample() {
+ const isDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
+
+ return (
+
+ {isDarkMode ? 'User prefers Dark mode' : 'User prefers Light mode'}
+
+ );
+}
+
+export function UseMediaQueryResponsiveExample() {
+ const isMobile = useMediaQuery('(max-width: 768px)');
+ const isTablet = useMediaQuery('(min-width: 769px) and (max-width: 1024px)');
+ const isDesktop = useMediaQuery('(min-width: 1025px)');
+
+ return (
+
+ {isMobile && '
'}
+ {isTablet && '
'}
+ {isDesktop && '
'}
+
+ );
+}
+
+export function UseMediaQueryPreferencesExample() {
+ const prefersReducedMotion = useMediaQuery(
+ '(prefers-reduced-motion: reduce)',
+ );
+
+ return (
+ Content
+ );
+}
+
+export function UseMediaQueryOrientationExample() {
+ const isPortrait = useMediaQuery('(orientation: portrait)');
+
+ return {isPortrait ? 'Portrait mode' : 'Landscape mode'}
;
+}
diff --git a/docs-ui/src/content/hooks/use-media-query.mdx b/docs-ui/src/content/hooks/use-media-query.mdx
new file mode 100644
index 0000000000..6941bc0d68
--- /dev/null
+++ b/docs-ui/src/content/hooks/use-media-query.mdx
@@ -0,0 +1,87 @@
+import { ChangelogComponent } from '@/components/ChangelogComponent';
+import { CodeBlock } from '@/components/CodeBlock';
+import { PageTitle } from '@/components/PageTitle';
+import { PropsTable } from '@/components/PropsTable';
+import {
+ useMediaQueryOrientationSnippet,
+ useMediaQueryParamDefs,
+ useMediaQueryPreferencesSnippet,
+ useMediaQueryResponsiveSnippet,
+ useMediaQueryReturnDefs,
+ useMediaQueryUsageSnippet,
+} from './use-media-query.props';
+import { Snippet } from '@/components/Snippet';
+import {
+ UseMediaQueryThemeExample,
+ UseMediaQueryPreferencesExample,
+ UseMediaQueryResponsiveExample,
+ UseMediaQueryOrientationExample,
+} from './use-media-query.example';
+
+
+
+## Usage
+
+The `useMediaQuery` hook allows you to evaluate CSS media queries in your components, enabling responsive design and behavior.
+
+}
+ code={useMediaQueryUsageSnippet}
+/>
+
+## Parameters
+
+
+
+## Return Value
+
+
+## Examples
+
+### Responsive Layouts
+
+Adapt layout based on different screen sizes.
+
+}
+ code={useMediaQueryResponsiveSnippet}
+/>
+
+### User Preferences
+
+Respect user system preferences.
+
+}
+ code={useMediaQueryPreferencesSnippet}
+/>
+
+### Device Orientation
+
+Detect screen orientation.
+
+}
+ code={useMediaQueryOrientationSnippet}
+/>
+
+
diff --git a/docs-ui/src/content/hooks/use-media-query.props.ts b/docs-ui/src/content/hooks/use-media-query.props.ts
new file mode 100644
index 0000000000..4a3bb1ca73
--- /dev/null
+++ b/docs-ui/src/content/hooks/use-media-query.props.ts
@@ -0,0 +1,90 @@
+import { type PropDef } from '@/utils/propDefs';
+
+export const useMediaQueryParamDefs: Record = {
+ query: {
+ type: 'string',
+ description: 'The CSS media query to evaluate',
+ },
+ options: {
+ type: 'complex',
+ complexType: {
+ name: 'UseMediaQueryOptions',
+ properties: {
+ defaultValue: {
+ type: 'boolean',
+ required: false,
+ description:
+ 'Default value to use when rendering on the server, defaults to false',
+ },
+ initializeWithValue: {
+ type: 'boolean',
+ required: false,
+ description:
+ 'Whether to initialize with the current value of the media query, or use the default value initially',
+ },
+ },
+ },
+ default: 'defaultValue: false\ninitializeWithValue: true',
+ },
+};
+
+export const useMediaQueryReturnDefs: Record = {
+ matches: {
+ type: 'boolean',
+ description: 'True if the media query currently matches',
+ },
+};
+
+export const useMediaQueryUsageSnippet = `import { useMediaQuery } from '@backstage/ui';
+
+function MyComponent() {
+ const isDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
+
+ return (
+
+ {isDarkMode ? 'Dark mode enabled' : 'Light mode enabled'}
+
+ );
+}`;
+
+export const useMediaQueryResponsiveSnippet = `import { useMediaQuery } from '@backstage/ui';
+
+function ResponsiveLayout() {
+ const isMobile = useMediaQuery('(max-width: 768px)');
+ const isTablet = useMediaQuery('(min-width: 769px) and (max-width: 1024px)');
+ const isDesktop = useMediaQuery('(min-width: 1025px)');
+
+ return (
+
+ {isMobile &&
}
+ {isTablet &&
}
+ {isDesktop &&
}
+
+ );
+}`;
+
+export const useMediaQueryPreferencesSnippet = `import { useMediaQuery } from '@backstage/ui';
+
+function AccessibleComponent() {
+ const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');
+
+ return (
+
+ Content
+
+ );
+}`;
+
+export const useMediaQueryOrientationSnippet = `import { useMediaQuery } from '@backstage/ui';
+
+function OrientationAware() {
+ const isPortrait = useMediaQuery('(orientation: portrait)');
+
+ return (
+
+ {isPortrait ? 'Portrait mode' : 'Landscape mode'}
+
+ );
+}`;
diff --git a/docs-ui/src/utils/data.ts b/docs-ui/src/utils/data.ts
index 7426b16130..bb1ea40dfb 100644
--- a/docs-ui/src/utils/data.ts
+++ b/docs-ui/src/utils/data.ts
@@ -138,3 +138,26 @@ export const components: Page[] = [
slug: 'visually-hidden',
},
];
+
+export const hooks: Page[] = [
+ {
+ title: 'useBreakpoint',
+ slug: 'use-breakpoint',
+ },
+ {
+ title: 'useIsomorphicLayoutEffect',
+ slug: 'use-isomorphic-layout-effect',
+ },
+ {
+ title: 'useMediaQuery',
+ slug: 'use-media-query',
+ },
+ {
+ title: 'useStyles',
+ slug: 'use-styles',
+ },
+ {
+ title: 'useSurface',
+ slug: 'use-surface',
+ },
+];