diff --git a/.changeset/README.md b/.changeset/README.md
index 4f3b76b096..6c68425e59 100644
--- a/.changeset/README.md
+++ b/.changeset/README.md
@@ -6,3 +6,58 @@ find the full documentation for it [in our repository](https://github.com/change
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/master/docs/common-questions.md)
+
+---
+
+## Backstage UI Changesets
+
+For `@backstage/ui` changesets, use this format:
+
+```markdown
+---
+'@backstage/ui': patch
+---
+
+Brief summary
+
+Optional description with code examples.
+
+**Migration:**
+
+Migration instructions (breaking changes only).
+
+**Affected components:** button, card
+```
+
+**Required:**
+
+- End with `**Affected components:**` + comma-separated component names
+- For breaking changes: Add `**Migration:**` section
+- No headings (`##`, `###`) inside - use bold markers
+
+**Examples:**
+
+```markdown
+Fixed button hover state
+
+**Affected components:** button
+```
+
+````markdown
+**BREAKING**: New Table API
+
+**Migration:**
+
+Update imports:
+
+```diff
+- import { Table } from '@backstage/ui';
++ import { Table, type ColumnConfig } from '@backstage/ui';
+```
+````
+
+**Affected components:** table
+
+```
+
+```
diff --git a/REVIEWING.md b/REVIEWING.md
index 66af880f43..27da917805 100644
--- a/REVIEWING.md
+++ b/REVIEWING.md
@@ -62,6 +62,37 @@ Some things that changeset should NOT contain are:
- Documentation - changesets can describe new features, but it should not be relied on for documenting them. Documentation should either be placed in [TSDoc](https://tsdoc.org) comments, package README, or [./docs/](./docs/).
- Diffs of internal code, for example mirroring what the pull request changes _inside_ a plugin rather than public surfaces. This is not of interest to the reader of a package changelog. Sometimes, however, a small and concise diff can be used in a changeset to illustrate changes that the user will have to make in _their own_ Backstage installation as part of an upgrade, specifically when breaking changes are made to a package.
+### Backstage UI Changeset Format
+
+Changesets for `@backstage/ui` must follow a standardized format to enable proper documentation generation. See [`.changeset/README.md`](.changeset/README.md#backstage-ui-changeset-format) for the complete guide.
+
+**Required structure:**
+
+```markdown
+---
+'@backstage/ui': patch
+---
+
+Brief one-line summary of the change
+
+Optional detailed description with any markdown content.
+
+**Migration:**
+
+Migration instructions (only for breaking changes)
+
+**Affected components:** Button, ButtonIcon
+```
+
+**Key requirements:**
+
+1. **Affected components marker** - Must end with `**Affected components:**` followed by comma-separated component names
+2. **Migration marker** - Use `**Migration:**` (bold, with colon) for breaking changes only
+3. **No headings** - Never use `##`, `###`, or `####` inside changeset entries (these break semantic structure when the entry becomes a list item in CHANGELOG.md)
+4. **Bold markers** - Use bold text markers, not plain text, for reliable parsing
+
+These markers enable the documentation system to extract metadata for per-component changelogs and separate migration guides.
+
### When is a changeset needed?
In general our changeset feedback bot will take care of informing whether a changeset is needed or not, but there are some edge cases. Whether a changeset is needed depends mostly on what files have been changed, but sometimes also on the kind of change that has been made.
diff --git a/docs-ui/scripts/sync-changelog.mjs b/docs-ui/scripts/sync-changelog.mjs
index 5eab3616dd..d405a74fd9 100644
--- a/docs-ui/scripts/sync-changelog.mjs
+++ b/docs-ui/scripts/sync-changelog.mjs
@@ -301,9 +301,11 @@ async function parseListItem(
// Remove the commit SHA prefix from markdown
let description = fullMarkdown.replace(/^-?\s*[a-f0-9]+:\s*/, '').trim();
- // Auto-detect components from "Affected components:" line
+ // Extract components using bold marker (standard format)
let components = [];
- const componentMatch = description.match(/Affected components?:\s*([^\n]+)/i);
+ const componentMatch = description.match(
+ /\*\*Affected components:\*\*\s*([^\n]+)/,
+ );
if (componentMatch) {
const componentNames = componentMatch[1]
.split(',')
@@ -314,9 +316,42 @@ async function parseListItem(
.map(name => mapComponentName(name, validComponents))
.filter(Boolean);
- // Optionally strip "Affected components:" line from description
+ // Strip "**Affected components:**" line from description
description = description
- .replace(/\n*Affected components?:[ \t]*[^\n]+/i, '')
+ .replace(/\n*\*\*Affected components:\*\*[ \t]*[^\n]+/g, '')
+ .trim();
+ } else {
+ // Fallback: try old format without bold markers
+ const oldFormatMatch = description.match(
+ /Affected components?:\s*([^\n]+)/i,
+ );
+ if (oldFormatMatch) {
+ const componentNames = oldFormatMatch[1]
+ .split(',')
+ .map(name => name.trim())
+ .filter(Boolean);
+
+ components = componentNames
+ .map(name => mapComponentName(name, validComponents))
+ .filter(Boolean);
+
+ // Strip old format line from description
+ description = description
+ .replace(/\n*Affected components?:[ \t]*[^\n]+/i, '')
+ .trim();
+ }
+ }
+
+ // Extract migration notes using bold marker (standard format)
+ let migration = null;
+ const migrationMatch = description.match(
+ /\*\*Migration:\*\*\s*\n([\s\S]+?)(?=\n\s*$|$)/,
+ );
+ if (migrationMatch) {
+ migration = migrationMatch[1].trim();
+ // Strip migration section from description
+ description = description
+ .replace(/\n*\*\*Migration:\*\*[\s\S]+$/, '')
.trim();
}
@@ -333,6 +368,7 @@ async function parseListItem(
components,
prs,
type,
+ migration,
};
}
@@ -467,12 +503,20 @@ async function generateVersionFiles(entries, changelogsDir, dryRun = false) {
.replace(/`/g, '\\`')
.replace(/\${/g, '\\${');
+ // Escape migration notes if present
+ const migrationStr = entry.migration
+ ? `migration: \`${entry.migration
+ .replace(/\\/g, '\\\\')
+ .replace(/`/g, '\\`')
+ .replace(/\${/g, '\\${')}\`,\n `
+ : '';
+
return ` {
components: ${componentsStr},
version: '${entry.version}',
prs: ${prsStr},
description: \`${descEscaped}\`,
- ${typeStr}
+ ${migrationStr}${typeStr}
${shaStr}
}`;
})
diff --git a/docs-ui/src/utils/types.ts b/docs-ui/src/utils/types.ts
index f2f773ebc2..173dcd4329 100644
--- a/docs-ui/src/utils/types.ts
+++ b/docs-ui/src/utils/types.ts
@@ -43,4 +43,5 @@ export interface ChangelogProps {
prs: string[];
type?: 'breaking' | 'new' | 'fix';
commitSha?: string;
+ migration?: string;
}
diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md
index 944c521afa..ae913d5e80 100644
--- a/packages/ui/CHANGELOG.md
+++ b/packages/ui/CHANGELOG.md
@@ -13,7 +13,7 @@
New features include unified pagination modes, debounced query changes, stale data preservation during reloads, and row selection with toggle/replace behaviors.
- **Migration guide:**
+ **Migration:**
1. Update imports and use the new `useTable` hook:
@@ -43,11 +43,11 @@
+
```
- Affected components: Table, TableRoot, TablePagination
+ **Affected components:** Table, TableRoot, TablePagination
- 95246eb: **Breaking** Updating color tokens to match the new neutral style on different surfaces.
- ## Migration notes
+ **Migration:**
There's no direct replacement for the old tint tokens but you can use the new neutral set of color tokens on surface 0 or 1 as a replacement.
@@ -64,11 +64,11 @@
- 1880402: Fixes app background color on dark mode.
- d2fdded: Added indeterminate state support to the Checkbox component for handling partial selection scenarios like table header checkboxes.
- Affected components: Checkbox
+ **Affected components:** Checkbox
- 4fb15d2: Added missing `aria-label` attributes to `SearchField` components in `Select`, `MenuAutocomplete`, and `MenuAutocompleteListbox` to fix accessibility warnings.
- Affected components: Select, MenuAutocomplete, MenuAutocompleteListbox
+ **Affected components:** Select, MenuAutocomplete, MenuAutocompleteListbox
- 21c87cc: Fixes disabled state in primary and secondary buttons in Backstage UI.
- 9c76682: build(deps-dev): bump `storybook` from 10.1.9 to 10.1.10
@@ -76,29 +76,29 @@
- 133d5c6: Added new Popover component for Backstage UI with automatic overflow handling, and full placement support. Also introduced `--bui-shadow` token for consistent elevation styling across overlay components (Popover, Tooltip, Menu).
- 973c839: Fixed Table sorting indicator not being visible when a column is actively sorted.
- Affected components: Table, Column
+ **Affected components:** Table, Column
- df40cfc: Fixed Menu component trigger button not toggling correctly. Removed custom click-outside handler that was interfering with React Aria's built-in state management, allowing the menu to properly open and close when clicking the trigger button.
- b01ab96: Added support for column width configuration in Table component. Columns now accept `width`, `defaultWidth`, `minWidth`, and `maxWidth` props for responsive layout control.
- Affected components: Table, Column
+ **Affected components:** Table, Column
- b4a4911: Fixed SearchField `startCollapsed` prop not working correctly in Backstage UI. The field now properly starts in a collapsed state, expands when clicked and focused, and collapses back when unfocused with no input. Also fixed CSS logic to work correctly in all layout contexts (flex row, flex column, and regular containers).
- Affected components: SearchField
+ **Affected components:** SearchField
- b3253b6: Fixed `Link` component causing hard page refreshes for internal routes. The component now properly uses React Router's navigation instead of full page reloads.
- fe7fe69: Added support for custom pagination options in `useTable` hook and `Table` component. You can now configure `pageSizeOptions` to customize the page size dropdown, and hook into pagination events via `onPageSizeChange`, `onNextPage`, and `onPreviousPage` callbacks. When `pageSize` doesn't match any option, the first option is used and a warning is logged.
- Affected components: Table, TablePagination
+ **Affected components:** Table, TablePagination
- cfac8a4: Fixed missing border styles on table selection cells in multi-select mode.
- Affected components: Table
+ **Affected components:** Table
- 2532d2a: Added `className` and `style` props to the `Table` component.
- Affected components: Table
+ **Affected components:** Table
## 0.11.0-next.1
@@ -113,7 +113,7 @@
New features include unified pagination modes, debounced query changes, stale data preservation during reloads, and row selection with toggle/replace behaviors.
- **Migration guide:**
+ **Migration:**
1. Update imports and use the new `useTable` hook:
@@ -143,11 +143,11 @@
+
```
- Affected components: Table, TableRoot, TablePagination
+ **Affected components:** Table, TableRoot, TablePagination
- 95246eb: **Breaking** Updating color tokens to match the new neutral style on different surfaces.
- ## Migration notes
+ **Migration:**
There's no direct replacement for the old tint tokens but you can use the new neutral set of color tokens on surface 0 or 1 as a replacement.
@@ -175,7 +175,7 @@
- 9c76682: build(deps-dev): bump `storybook` from 10.1.9 to 10.1.10
- b4a4911: Fixed SearchField `startCollapsed` prop not working correctly in Backstage UI. The field now properly starts in a collapsed state, expands when clicked and focused, and collapses back when unfocused with no input. Also fixed CSS logic to work correctly in all layout contexts (flex row, flex column, and regular containers).
- Affected components: SearchField
+ **Affected components:** SearchField
## 0.10.0
@@ -183,7 +183,7 @@
- 16543fa: **Breaking change** The `Cell` component has been refactored to be a generic wrapper component that accepts `children` for custom cell content. The text-specific functionality (previously part of `Cell`) has been moved to a new `CellText` component.
- ### Migration Guide
+ **Migration:**
If you were using `Cell` with text-specific props (`title`, `description`, `leadingIcon`, `href`), you need to update your code to use `CellText` instead:
@@ -219,24 +219,24 @@
- 50b7927: Fixed Checkbox indicator showing checkmark color when unchecked.
- Affected components: Checkbox
+ **Affected components:** Checkbox
- 5bacf55: Fixed `ButtonIcon` incorrectly applying `className` to inner elements instead of only the root element.
- Affected components: ButtonIcon
+ **Affected components:** ButtonIcon
- b3ad928: Fixed Table Row component to correctly handle cases where no `href` is provided, preventing unnecessary router provider wrapping and fixing the cursor incorrectly showing as a pointer despite the element not being a link.
- Affected components: Row
+ **Affected components:** Row
- a20d317: Added row selection support with visual state styling for hover, selected, and pressed states. Fixed checkbox rendering to only show for multi-select toggle mode.
- Affected components: Table, TableHeader, Row, Column
+ **Affected components:** Table, TableHeader, Row, Column
- fe7c751: Fixed `useTable` hook to prioritize `providedRowCount` over data length for accurate row count in server-side pagination scenarios.
- c145031: Fixed Table column sorting indicator to show up arrow when no sort is active, correctly indicating that clicking will sort ascending.
- Affected components: Column
+ **Affected components:** Column
## 0.10.0-next.1
@@ -244,7 +244,7 @@
- 16543fa: **Breaking change** The `Cell` component has been refactored to be a generic wrapper component that accepts `children` for custom cell content. The text-specific functionality (previously part of `Cell`) has been moved to a new `CellText` component.
- ### Migration Guide
+ **Migration:**
If you were using `Cell` with text-specific props (`title`, `description`, `leadingIcon`, `href`), you need to update your code to use `CellText` instead:
@@ -280,15 +280,15 @@
- 50b7927: Fixed Checkbox indicator showing checkmark color when unchecked.
- Affected components: Checkbox
+ **Affected components:** Checkbox
- 5bacf55: Fixed `ButtonIcon` incorrectly applying `className` to inner elements instead of only the root element.
- Affected components: ButtonIcon
+ **Affected components:** ButtonIcon
- a20d317: Added row selection support with visual state styling for hover, selected, and pressed states. Fixed checkbox rendering to only show for multi-select toggle mode.
- Affected components: Table, TableHeader, Row, Column
+ **Affected components:** Table, TableHeader, Row, Column
## 0.9.1-next.0
@@ -296,12 +296,12 @@
- b3ad928: Fixed Table Row component to correctly handle cases where no `href` is provided, preventing unnecessary router provider wrapping and fixing the cursor incorrectly showing as a pointer despite the element not being a link.
- Affected components: Row
+ **Affected components:** Row
- fe7c751: Fixed `useTable` hook to prioritize `providedRowCount` over data length for accurate row count in server-side pagination scenarios.
- c145031: Fixed Table column sorting indicator to show up arrow when no sort is active, correctly indicating that clicking will sort ascending.
- Affected components: Column
+ **Affected components:** Column
## 0.9.0
@@ -317,7 +317,7 @@
- `large` size **changed from 3rem to 2.5rem** (40px)
- New `x-large` size added (3rem / 48px)
- Migration:
+ **Migration:**
```diff
# Remove Base UI-specific props
@@ -344,7 +344,7 @@
- Data attribute: `data-checked` → `data-selected`
- Use without label is no longer supported
- Migration examples:
+ **Migration:**
Before:
@@ -400,21 +400,15 @@
- b78fc45: **BREAKING**: Changed className prop behavior to augment default styles instead of being ignored or overriding them.
- Affected components:
-
- - Menu, MenuListBox, MenuAutocomplete, MenuAutocompleteListbox, MenuItem, MenuListBoxItem, MenuSection, MenuSeparator
- - Switch
- - Skeleton
- - FieldLabel
- - Header, HeaderToolbar
- - HeaderPage
- - Tabs, TabList, Tab, TabPanel
-
If you were passing custom className values to any of these components that relied on the previous behavior, you may need to adjust your styles to account for the default classes now being applied alongside your custom classes.
+ **Affected components:** Menu, MenuListBox, MenuAutocomplete, MenuAutocompleteListbox, MenuItem, MenuListBoxItem, MenuSection, MenuSeparator, Switch, Skeleton, FieldLabel, Header, HeaderToolbar, HeaderPage, Tabs, TabList, Tab, TabPanel
+
- 83c100e: **BREAKING**: Removed `Collapsible` component. Migrate to `Accordion` or use React Aria `Disclosure`.
- ## Migration Path 1: Accordion (Opinionated Styled Component)
+ **Migration:**
+
+ **Path 1: Accordion (Opinionated Styled Component)**
Accordion provides preset styling with a similar component structure.
@@ -435,7 +429,7 @@
CSS classes: `.bui-CollapsibleRoot` → `.bui-Accordion`, `.bui-CollapsibleTrigger` → `.bui-AccordionTrigger` (now on heading element), `.bui-CollapsiblePanel` → `.bui-AccordionPanel`
- ## Migration Path 2: React Aria Disclosure (Full Customization)
+ **Path 2: React Aria Disclosure (Full Customization)**
For custom styling without preset styles:
@@ -452,7 +446,9 @@
Added searchable and multiple selection support to Select component. The component now accepts `searchable`, `selectionMode`, and `searchPlaceholder` props to enable filtering and multi-selection modes.
- Migration: If you're using `SelectProps` type directly, update from `SelectProps` to `SelectProps<'single' | 'multiple'>`. Component usage remains backward compatible.
+ **Migration:**
+
+ If you're using `SelectProps` type directly, update from `SelectProps` to `SelectProps<'single' | 'multiple'>`. Component usage remains backward compatible.
### Patch Changes
@@ -483,7 +479,9 @@
- 83c100e: **BREAKING**: Removed `Collapsible` component. Migrate to `Accordion` or use React Aria `Disclosure`.
- ## Migration Path 1: Accordion (Opinionated Styled Component)
+ **Migration:**
+
+ **Path 1: Accordion (Opinionated Styled Component)**
Accordion provides preset styling with a similar component structure.
@@ -504,7 +502,7 @@
CSS classes: `.bui-CollapsibleRoot` → `.bui-Accordion`, `.bui-CollapsibleTrigger` → `.bui-AccordionTrigger` (now on heading element), `.bui-CollapsiblePanel` → `.bui-AccordionPanel`
- ## Migration Path 2: React Aria Disclosure (Full Customization)
+ **Path 2: React Aria Disclosure (Full Customization)**
For custom styling without preset styles:
@@ -521,7 +519,9 @@
Added searchable and multiple selection support to Select component. The component now accepts `searchable`, `selectionMode`, and `searchPlaceholder` props to enable filtering and multi-selection modes.
- Migration: If you're using `SelectProps` type directly, update from `SelectProps` to `SelectProps<'single' | 'multiple'>`. Component usage remains backward compatible.
+ **Migration:**
+
+ If you're using `SelectProps` type directly, update from `SelectProps` to `SelectProps<'single' | 'multiple'>`. Component usage remains backward compatible.
### Patch Changes
@@ -546,7 +546,7 @@
- `large` size **changed from 3rem to 2.5rem** (40px)
- New `x-large` size added (3rem / 48px)
- Migration:
+ **Migration:**
```diff
# Remove Base UI-specific props
@@ -589,7 +589,7 @@
- Data attribute: `data-checked` → `data-selected`
- Use without label is no longer supported
- Migration examples:
+ **Migration:**
Before:
@@ -633,18 +633,10 @@
- b78fc45: **BREAKING**: Changed className prop behavior to augment default styles instead of being ignored or overriding them.
- Affected components:
-
- - Menu, MenuListBox, MenuAutocomplete, MenuAutocompleteListbox, MenuItem, MenuListBoxItem, MenuSection, MenuSeparator
- - Switch
- - Skeleton
- - FieldLabel
- - Header, HeaderToolbar
- - HeaderPage
- - Tabs, TabList, Tab, TabPanel
-
If you were passing custom className values to any of these components that relied on the previous behavior, you may need to adjust your styles to account for the default classes now being applied alongside your custom classes.
+ **Affected components:** Menu, MenuListBox, MenuAutocomplete, MenuAutocompleteListbox, MenuItem, MenuListBoxItem, MenuSection, MenuSeparator, Switch, Skeleton, FieldLabel, Header, HeaderToolbar, HeaderPage, Tabs, TabList, Tab, TabPanel
+
### Patch Changes
- ff9f0c3: Enable tree-shaking of imports other than `*.css`.