feat: add useStyles hook documentation & disclaimer for internal hooks

Signed-off-by: Antony Bouyon <antony.bouyon@believe.com>
This commit is contained in:
Antony Bouyon
2026-01-21 13:16:46 +01:00
parent 6b98a35341
commit a57041b196
5 changed files with 229 additions and 11 deletions
+1 -11
View File
@@ -18,17 +18,7 @@ Backstage UI custom hooks provide easy access to design system features, style m
/>
<ComponentCard
title="useStyles"
description="Main hook to access and manipulate Backstage design system styles."
description="Main hook to access and manipulate design system styles."
href="/hooks/use-styles"
/>
<ComponentCard
title="useSurface"
description="Hook to manage surfaces and elevation levels in the interface."
href="/hooks/use-surface"
/>
<ComponentCard
title="useIsomorphicLayoutEffect"
description="Isomorphic version of useLayoutEffect that works on both client and server."
href="/hooks/use-isomorphic-layout-effect"
/>
</ComponentCards>
@@ -3,6 +3,7 @@ import { CodeBlock } from '@/components/CodeBlock';
import { PageTitle } from '@/components/PageTitle';
import { PropsTable } from '@/components/PropsTable';
import { Snippet } from '@/components/Snippet';
import { Banner } from '@/components/Banner';
import {
UseMediaQueryOrientationExample,
UseMediaQueryPreferencesExample,
@@ -25,6 +26,11 @@ import {
description="Hook to evaluate and react to CSS media queries programmatically."
/>
<Banner
variant="warning"
text="This hook is currently intended for internal use within the design system."
/>
## Usage
The `useMediaQuery` hook allows you to evaluate CSS media queries in your components, enabling responsive design and behavior.
+59
View File
@@ -0,0 +1,59 @@
import { ChangelogComponent } from '@/components/ChangelogComponent';
import { CodeBlock } from '@/components/CodeBlock';
import { Banner } from '@/components/Banner';
import { PageTitle } from '@/components/PageTitle';
import { PropsTable } from '@/components/PropsTable';
import {
useStylesUsageSnippet,
useStylesDefsSnippet,
useStylesAttributesSnippet,
} from './snippets';
import { useStylesPropsDefs, useStylesReturnDefs } from './props-definition';
<PageTitle
title="useStyles"
description="React hook to get class names and data attributes for a component with responsive support"
/>
<Banner
variant="warning"
text="This hook is currently intended for internal use within the design system."
/>
## Usage
The `useStyles` hook is the core styling utility that processes component definitions and props to generate:
- **Class names**
- **Data attributes** for state-based styling (e.g., `data-disabled`, `data-variant`)
- **Utility classes** from utility props like `padding`, `margin`, `display` (with responsive support)
- **CSS custom properties** for custom values not in predefined utility lists
- **Cleaned props** with utility props removed (safe to spread onto DOM elements)
<CodeBlock code={useStylesUsageSnippet} />
## Parameters
<PropsTable data={useStylesPropsDefs} isHook />
## Return Value
<PropsTable data={useStylesReturnDefs} isHook />
## How It Works
### Component Definition
A component definition describes how a component should be styled:
<CodeBlock code={useStylesDefsSnippet} />
<Banner variant="info" text="The data-attributes definition should be kebab-case instead of camelCase." />
### Data Attributes
Data attributes enable state-based styling through CSS selectors. The hook automatically adds `data-*` to the attributes based on the defined props:
<CodeBlock code={useStylesAttributesSnippet} />
<ChangelogComponent component="use-styles" />
@@ -0,0 +1,58 @@
import { type PropDef } from '@/utils/propDefs';
export const useStylesPropsDefs: Record<string, PropDef> = {
componentDefinition: {
type: 'complex',
complexType: {
name: 'ComponentDefinition',
properties: {
classNames: {
type: 'Record<string, string>',
required: true,
},
dataAttributes: {
type: 'Record<string, readonly (string | number | boolean)[]>',
required: false,
},
utilityProps: {
type: 'string[]',
required: false,
},
},
},
},
props: {
type: 'enum',
description: 'All component props',
values: ['{ [key: string]: any; }'],
},
};
export const useStylesReturnDefs: Record<string, PropDef> = {
classNames: {
type: 'enum',
values: ['Record<string, string>'],
description:
"The component's class names mapped to their style definitions",
},
dataAttributes: {
type: 'enum',
values: ['Record<string, string>'],
description:
'Data attributes generated from the component definition and props',
},
utilityClasses: {
type: 'string',
description: 'Combined utility classes based on utility props',
},
style: {
type: 'enum',
values: ['React.CSSProperties'],
description: 'The combined style object for the component',
},
cleanedProps: {
type: 'enum',
values: ['Record<string, any>'],
description: 'The original props with utility props removed',
},
};
@@ -0,0 +1,105 @@
export const useStylesUsageSnippet = `import { useStyles } from '@backstage/ui';
import type {
ComponentDefinition,
SpaceProps,
UtilityProps,
} from '@backstage/ui';
// Define your component's styling configuration
const buttonDefinition: ComponentDefinition = {
classNames: {
root: 'bui-button',
icon: 'bui-button-icon',
},
dataAttributes: {
variant: ['primary', 'secondary', 'ghost'] as const,
size: ['small', 'medium', 'large'] as const,
'is-disabled': [true, false] as const,
},
utilityProps: ['gap', 'mb', 'mt', 'ml', 'mr'],
} as const satisfies ComponentDefinition;
// type the component props
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'ghost';
size?: 'small' | 'medium' | 'large';
isDisabled?: boolean;
gap?: UtilityProps['gap'];
mb?: SpaceProps['mb'];
mt?: SpaceProps['mt'];
ml?: SpaceProps['ml'];
mr?: SpaceProps['mr'];
icon?: React.ReactElement;
children?: React.ReactNode;
}
// Use the useStyles hook in your component
export function Button({
variant = 'primary',
size = 'medium',
isDisabled,
...props
}: Readonly<ButtonProps>) {
const { classNames, dataAttributes, utilityClasses, cleanedProps } =
useStyles(buttonDefinition, {
variant,
size,
'is-disabled': isDisabled,
...props,
});
return (
<button
className={\`\${classNames.root} \${utilityClasses}\`}
{...dataAttributes}
{...cleanedProps}
>
{props.icon ?? <span className={classNames.icon}>{props.icon}</span>}
{props.children}
</button>
);
}`;
export const useStylesDefsSnippet = `const componentDefinition = {
// CSS class names for component parts
classNames: {
root: 'bui-button',
icon: 'bui-button-icon',
},
// Props that become data attributes for CSS targeting
// data-attribute must be kebab-case to respect HTML standards
// And casing matters here, lowercase only:
// 'variant' not 'Variant', 'is-disabled' not 'isDisabled'..
dataAttributes: {
variant: ['primary', 'secondary'] as const,
size: ['small', 'large'] as const,
'is-disabled': [true, false] as const,
},
// Props that become utility classes (spacing, layout, etc.)
utilityProps: ['m', 'mb', 'mt', 'ml', 'mr'],
};`;
export const useStylesAttributesSnippet = `/* Component CSS can use data attributes for variants */
.bui-button {
padding: 0.5rem 1rem;
border-radius: 0.25rem;
}
/* Variant styles */
.bui-button[data-variant="primary"] {
background: blue;
color: white;
}
.bui-button[data-variant="secondary"] {
background: gray;
color: black;
}
/* State styles */
.bui-button[data-is-disabled="true"] {
opacity: 0.5;
pointer-events: none;
}`;