From 872dc1025e4b948200aa8cf440a2daefb3793fbe Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 16 Mar 2026 20:16:25 +0100 Subject: [PATCH 1/3] ui: document table cell wrapper requirement Added TSDoc comments to ColumnConfig.cell, RowRenderFn, CellProps, CellTextProps, and CellProfileProps making it explicit that cell render functions must return a cell component (Cell, CellText, or CellProfile) as the top-level element. Also added a Table Cell Requirement section to the package README with correct and incorrect usage examples. Signed-off-by: Patrik Oldsberg Made-with: Cursor --- .changeset/document-table-cell-requirement.md | 7 ++ packages/ui/README.md | 28 +++++++ packages/ui/report.api.md | 17 ++-- packages/ui/src/components/Table/types.ts | 82 +++++++++++++++++-- 4 files changed, 117 insertions(+), 17 deletions(-) create mode 100644 .changeset/document-table-cell-requirement.md diff --git a/.changeset/document-table-cell-requirement.md b/.changeset/document-table-cell-requirement.md new file mode 100644 index 0000000000..8a8b82bc90 --- /dev/null +++ b/.changeset/document-table-cell-requirement.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Documented the requirement that `ColumnConfig.cell` and custom `RowRenderFn` renders must return a cell component (`Cell`, `CellText`, or `CellProfile`) as the top-level element. + +Affected components: Table, Cell, CellText, CellProfile diff --git a/packages/ui/README.md b/packages/ui/README.md index 271b78fca4..25de0f3190 100644 --- a/packages/ui/README.md +++ b/packages/ui/README.md @@ -17,6 +17,34 @@ yarn add @backstage/ui - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) - [Backstage Documentation](https://backstage.io/docs) +## Table Cell Requirement + +When using the `Table` component, every cell rendered via `ColumnConfig.cell` (or +inside a custom `RowRenderFn`) **must** return a cell component as the top-level +element. The available cell components are: + +- **`CellText`** — displays a title with optional description and icon. +- **`CellProfile`** — displays an avatar with a name and optional description. +- **`Cell`** — a generic wrapper for fully custom cell content. + +Returning bare text, React fragments, or other elements without wrapping them in +one of these cell components will break the table layout. + +```tsx +// ✅ Correct — CellText is the top-level element +cell: item => ; + +// ✅ Correct — Cell wraps custom content +cell: item => ( + + + +); + +// ❌ Wrong — bare text without a cell wrapper +cell: item => {item.name}; +``` + ## Writing Changesets for Components When creating changesets for component-specific changes, add component metadata to help maintain documentation: diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index b254b4f773..f55efe8c89 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -766,7 +766,7 @@ export const Cell: { displayName: string; }; -// @public (undocumented) +// @public export type CellOwnProps = { className?: string; }; @@ -774,7 +774,7 @@ export type CellOwnProps = { // @public (undocumented) export const CellProfile: (props: CellProfileProps) => JSX_2.Element; -// @public (undocumented) +// @public export type CellProfileOwnProps = { src?: string; name?: string; @@ -784,12 +784,12 @@ export type CellProfileOwnProps = { className?: string; }; -// @public (undocumented) +// @public export interface CellProfileProps extends CellProfileOwnProps, Omit {} -// @public (undocumented) +// @public export interface CellProps extends CellOwnProps, Omit {} @@ -800,7 +800,7 @@ export const CellText: { displayName: string; }; -// @public (undocumented) +// @public export type CellTextOwnProps = { title: string; description?: string; @@ -810,7 +810,7 @@ export type CellTextOwnProps = { className?: string; }; -// @public (undocumented) +// @public export interface CellTextProps extends CellTextOwnProps, Omit {} @@ -849,9 +849,8 @@ export interface CheckboxProps // @public (undocumented) export const Column: (props: ColumnProps) => JSX_2.Element; -// @public (undocumented) +// @public export interface ColumnConfig { - // (undocumented) cell: (item: T) => ReactElement; // (undocumented) defaultWidth?: ColumnSize | null; @@ -2068,7 +2067,7 @@ export interface RowProps extends RowOwnProps, Omit, keyof RowOwnProps> {} -// @public (undocumented) +// @public export type RowRenderFn = (params: { item: T; index: number; diff --git a/packages/ui/src/components/Table/types.ts b/packages/ui/src/components/Table/types.ts index 0736afa05b..030ac44ae0 100644 --- a/packages/ui/src/components/Table/types.ts +++ b/packages/ui/src/components/Table/types.ts @@ -93,17 +93,35 @@ export interface ColumnProps extends ColumnOwnProps, Omit {} -/** @public */ +/** + * Own props for the {@link Cell} component. + * + * @public + */ export type CellOwnProps = { className?: string; }; -/** @public */ +/** + * Props for the {@link Cell} component. + * + * `Cell` is a generic cell wrapper for custom cell content. When rendering + * cells via {@link ColumnConfig.cell} or a custom {@link RowRenderFn}, the + * returned element **must** be a cell component (`Cell`, `CellText`, or + * `CellProfile`) at the top level. Returning bare text or other elements + * without a cell wrapper will break the table layout. + * + * @public + */ export interface CellProps extends CellOwnProps, Omit {} -/** @public */ +/** + * Own props for the {@link CellText} component. + * + * @public + */ export type CellTextOwnProps = { title: string; description?: string; @@ -113,12 +131,25 @@ export type CellTextOwnProps = { className?: string; }; -/** @public */ +/** + * Props for the {@link CellText} component. + * + * `CellText` renders a table cell with a title and optional description. It + * is one of the cell components (`Cell`, `CellText`, `CellProfile`) that + * **must** be used as the top-level element returned from + * {@link ColumnConfig.cell} or a custom {@link RowRenderFn}. + * + * @public + */ export interface CellTextProps extends CellTextOwnProps, Omit {} -/** @public */ +/** + * Own props for the {@link CellProfile} component. + * + * @public + */ export type CellProfileOwnProps = { src?: string; name?: string; @@ -128,7 +159,16 @@ export type CellProfileOwnProps = { className?: string; }; -/** @public */ +/** + * Props for the {@link CellProfile} component. + * + * `CellProfile` renders a table cell with an avatar, name, and optional + * description. It is one of the cell components (`Cell`, `CellText`, + * `CellProfile`) that **must** be used as the top-level element returned + * from {@link ColumnConfig.cell} or a custom {@link RowRenderFn}. + * + * @public + */ export interface CellProfileProps extends CellProfileOwnProps, Omit {} @@ -151,10 +191,27 @@ export interface PagePagination extends TablePaginationProps { /** @public */ export type TablePaginationType = NoPagination | PagePagination; -/** @public */ +/** + * Configuration for a single table column. + * + * @public + */ export interface ColumnConfig { id: string; label: string; + /** + * Renders the cell content for this column. + * + * **Important:** The returned element **must** be a cell component at the + * top level — either `Cell`, `CellText`, or `CellProfile`. Returning bare + * text, fragments, or other elements without a cell wrapper will break the + * table layout. + * + * @example + * ```tsx + * cell: item => + * ``` + */ cell: (item: T) => ReactElement; header?: () => ReactElement; isSortable?: boolean; @@ -173,7 +230,16 @@ export interface RowConfig { getIsDisabled?: (item: T) => boolean; } -/** @public */ +/** + * Custom render function for table rows. + * + * When using a custom row render function, each cell rendered inside the row + * **must** use a cell component (`Cell`, `CellText`, or `CellProfile`) as + * the top-level element. Returning bare text or other elements without a + * cell wrapper will break the table layout. + * + * @public + */ export type RowRenderFn = (params: { item: T; index: number; From 1f25382e569f3eb3f09556f8c1006f7a2a4a3033 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 16 Mar 2026 20:27:02 +0100 Subject: [PATCH 2/3] Move table cell requirement docs to docs-ui Move the cell wrapper requirement documentation from the package README to the docs-ui table component page where it belongs. Signed-off-by: Patrik Oldsberg Made-with: Cursor --- .changeset/document-table-cell-requirement.md | 7 ----- docs-ui/src/app/components/table/page.mdx | 13 +++++++++ docs-ui/src/app/components/table/snippets.ts | 17 +++++++++++ packages/ui/README.md | 28 ------------------- 4 files changed, 30 insertions(+), 35 deletions(-) delete mode 100644 .changeset/document-table-cell-requirement.md diff --git a/.changeset/document-table-cell-requirement.md b/.changeset/document-table-cell-requirement.md deleted file mode 100644 index 8a8b82bc90..0000000000 --- a/.changeset/document-table-cell-requirement.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Documented the requirement that `ColumnConfig.cell` and custom `RowRenderFn` renders must return a cell component (`Cell`, `CellText`, or `CellProfile`) as the top-level element. - -Affected components: Table, Cell, CellText, CellProfile diff --git a/docs-ui/src/app/components/table/page.mdx b/docs-ui/src/app/components/table/page.mdx index 6eb280cbe2..8d0e803bd2 100644 --- a/docs-ui/src/app/components/table/page.mdx +++ b/docs-ui/src/app/components/table/page.mdx @@ -42,6 +42,7 @@ import { tableCombinedSnippet, tableCustomRowSnippet, tablePrimitivesSnippet, + tableCellRequirementSnippet, } from './snippets'; import { ChangelogComponent } from '@/components/ChangelogComponent'; import { PageTitle } from '@/components/PageTitle'; @@ -94,6 +95,18 @@ For full control over state, use the controlled props instead: - `search` / `onSearchChange` - `filter` / `onFilterChange` +### Cell Requirement + +Every cell rendered via `ColumnConfig.cell` (or inside a custom `RowRenderFn`) **must** return a cell component as the top-level element. The available cell components are: + +- **`CellText`** — displays a title with optional description and icon. +- **`CellProfile`** — displays an avatar with a name and optional description. +- **`Cell`** — a generic wrapper for fully custom cell content. + +Returning bare text, React fragments, or other elements without wrapping them in one of these cell components will break the table layout. + + + ## Common Patterns ### Sorting diff --git a/docs-ui/src/app/components/table/snippets.ts b/docs-ui/src/app/components/table/snippets.ts index ab4bf53a58..39f266f2ea 100644 --- a/docs-ui/src/app/components/table/snippets.ts +++ b/docs-ui/src/app/components/table/snippets.ts @@ -57,6 +57,23 @@ const { filter, // { value, onChange } for filters } = useTable({ ... });`; +// ============================================================================= +// Cell Requirement +// ============================================================================= + +export const tableCellRequirementSnippet = `// ✅ Correct — CellText is the top-level element +cell: item => ; + +// ✅ Correct — Cell wraps custom content +cell: item => ( + + + +); + +// ❌ Wrong — bare text without a cell wrapper +cell: item => {item.name};`; + // ============================================================================= // Common Patterns // ============================================================================= diff --git a/packages/ui/README.md b/packages/ui/README.md index 25de0f3190..271b78fca4 100644 --- a/packages/ui/README.md +++ b/packages/ui/README.md @@ -17,34 +17,6 @@ yarn add @backstage/ui - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) - [Backstage Documentation](https://backstage.io/docs) -## Table Cell Requirement - -When using the `Table` component, every cell rendered via `ColumnConfig.cell` (or -inside a custom `RowRenderFn`) **must** return a cell component as the top-level -element. The available cell components are: - -- **`CellText`** — displays a title with optional description and icon. -- **`CellProfile`** — displays an avatar with a name and optional description. -- **`Cell`** — a generic wrapper for fully custom cell content. - -Returning bare text, React fragments, or other elements without wrapping them in -one of these cell components will break the table layout. - -```tsx -// ✅ Correct — CellText is the top-level element -cell: item => ; - -// ✅ Correct — Cell wraps custom content -cell: item => ( - - - -); - -// ❌ Wrong — bare text without a cell wrapper -cell: item => {item.name}; -``` - ## Writing Changesets for Components When creating changesets for component-specific changes, add component metadata to help maintain documentation: From 0ebde15eef1c91cbb07b727a93f4cb305889ba96 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 16 Mar 2026 20:39:11 +0100 Subject: [PATCH 3/3] Add changeset for table cell documentation Signed-off-by: Patrik Oldsberg Made-with: Cursor --- .changeset/document-table-cell-requirement.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/document-table-cell-requirement.md diff --git a/.changeset/document-table-cell-requirement.md b/.changeset/document-table-cell-requirement.md new file mode 100644 index 0000000000..fd99de519f --- /dev/null +++ b/.changeset/document-table-cell-requirement.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Added documentation for the table cell wrapper requirement to TSDoc comments for `Cell`, `CellText`, `CellProfile`, `ColumnConfig`, and `RowRenderFn`.