Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions pages/key-value-pairs/permutations.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,16 @@ const permutations = createPermutations<KeyValuePairsProps>([
],
],
},
{
columns: [5, 6],
minColumnWidth: [undefined, 100],
items: [
Array.from({ length: 7 }, (_, i) => ({
label: `Label for key ${i + 1}`,
value: `Value ${i + 1}`,
})),
],
},
]);

export default function KeyValueScenario() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18611,8 +18611,9 @@ Don't use \`ariaLabel\` and \`ariaLabelledby\` at the same time.",
},
{
"defaultValue": "1",
"description": "Specifies the number of columns in each grid row.
Valid values are any integer between 1 and 4. It defaults to 1.",
"description": "Specifies the number of columns in each grid row. It defaults to 1.

The number of columns is reduced as the available space narrows, based on \`minColumnWidth\`.",
"name": "columns",
"optional": true,
"type": "number",
Expand Down Expand Up @@ -18652,7 +18653,10 @@ Each group definition has the following properties:

The number of columns is determined by the value of this property, the available space,
and the maximum number of columns as defined by the \`columns\` property.
If not set, defaults to 150.",
If not set, defaults to 150.

While reducing the count, odd numbers are skipped, so 5 columns becomes 4 and then 2.
A requested odd count is kept when it fits.",
"name": "minColumnWidth",
"optional": true,
"type": "number",
Expand Down
34 changes: 19 additions & 15 deletions src/key-value-pairs/__tests__/key-value-pairs.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,22 +106,26 @@ describe('KeyValuePairs', () => {
});
});

describe('warnOnce when columns property exceeds max', () => {
test.each([
{ columns: 1, warnOnceMessage: false },
{ columns: 2, warnOnceCalled: false },
{
columns: 3,
warnOnceCalled: false,
},
{ columns: 4, warnOnceCalled: false },
{
columns: 5,
warnOnceCalled: true,
},
])(`warnOnce called = $warnOnceCalled when columns is set to $columns`, ({ columns, warnOnceCalled }) => {
describe('columns property', () => {
test.each([1, 2, 3, 4, 5, 6, 12])('does not warn when columns is set to %i', columns => {
renderKeyValuePairs(<KeyValuePairs items={[]} columns={columns} />);
expect(warnOnce).toHaveBeenCalledTimes(warnOnceCalled ? 1 : 0);
expect(warnOnce).not.toHaveBeenCalled();
});

// Asserts the resolved grid rather than the number of items, because every item renders either
// way
test.each([3, 6, 12])('lays out %i columns', columns => {
const { wrapper } = renderKeyValuePairs(
<KeyValuePairs
columns={columns}
items={Array.from({ length: columns }, (_, index) => ({
label: `Label ${index}`,
value: `Value ${index}`,
}))}
/>
);

expect(wrapper.find('dl')!.getElement().style.gridTemplateColumns).toBe(`repeat(${columns}, minmax(0, 1fr))`);
});
Comment thread
vikaskale2722 marked this conversation as resolved.
Resolved
});

Expand Down
8 changes: 6 additions & 2 deletions src/key-value-pairs/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import { BaseComponentProps } from '../types/base-component';

export interface KeyValuePairsProps extends BaseComponentProps {
/**
* Specifies the number of columns in each grid row.
* Valid values are any integer between 1 and 4. It defaults to 1.
* Specifies the number of columns in each grid row. It defaults to 1.
*
* The number of columns is reduced as the available space narrows, based on `minColumnWidth`.
*/
columns?: number;
/**
Expand Down Expand Up @@ -46,6 +47,9 @@ export interface KeyValuePairsProps extends BaseComponentProps {
* The number of columns is determined by the value of this property, the available space,
* and the maximum number of columns as defined by the `columns` property.
* If not set, defaults to 150.
*
* While reducing the count, odd numbers are skipped, so 5 columns becomes 4 and then 2.
* A requested odd count is kept when it fits.
*/
minColumnWidth?: number;
}
Expand Down
25 changes: 6 additions & 19 deletions src/key-value-pairs/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import React from 'react';
import clsx from 'clsx';

import { useUniqueId, warnOnce } from '@cloudscape-design/component-toolkit/internal';
import { useUniqueId } from '@cloudscape-design/component-toolkit/internal';

import Box from '../box/internal';
import ColumnLayout from '../column-layout/internal';
Expand Down Expand Up @@ -48,18 +48,9 @@ const InternalKeyValuePairs = React.forwardRef(
ariaLabelledby,
minColumnWidth,
...rest
}: KeyValuePairsProps & Required<Pick<KeyValuePairsProps, 'columns'>>,
}: KeyValuePairsProps & Required<Pick<KeyValuePairsProps, 'columns' | 'minColumnWidth'>>,
ref: React.Ref<HTMLDivElement>
) => {
const MAX_COLUMNS = 4;

if (columns > MAX_COLUMNS) {
warnOnce(
'Key-value pairs',
`\`columns\` (${columns}) must be <= ${MAX_COLUMNS}. Using ${MAX_COLUMNS} as default.`
);
}

return (
<LinkDefaultVariantContext.Provider value={{ defaultVariant: 'primary' }}>
<div
Expand All @@ -70,15 +61,11 @@ const InternalKeyValuePairs = React.forwardRef(
ref={ref}
>
{/*
minColumnWidth={150} is set to use FlexibleColumnLayout which has only 1 nested div wrapper for column items,
otherwise GridColumnLayout will be used, which has 2 nested div, therefore it is not a11y compatible for dl -> dt/dd relationship
A minColumnWidth is always passed (KeyValuePairs defaults it to 150) so that ColumnLayout
picks FlexibleColumnLayout, which wraps each column item in a single div. GridColumnLayout
wraps in two, which breaks the dl -> dt/dd relationship a11y relies on.
*/}
<ColumnLayout
__tagOverride="dl"
columns={Math.min(columns, MAX_COLUMNS)}
variant="text-grid"
minColumnWidth={minColumnWidth}
>
<ColumnLayout __tagOverride="dl" columns={columns} variant="text-grid" minColumnWidth={minColumnWidth}>
Comment thread
vikaskale2722 marked this conversation as resolved.
Resolved
Comment thread
vikaskale2722 marked this conversation as resolved.
Resolved

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if a consumer passes minColumnWidth={0}

@vikaskale2722 vikaskale2722 Oct 30, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will internally use GridColumnLayout (instead of FlexibleColumnLayout which it uses with minColumnWidth) and beyond 4 columns it will cause unexpected behavior. But passing 0 as a input to the column width does not make sense.
Do you think we should add a guardrail?

{items.map((pair, index) => {
if (pair.type === 'group') {
return (
Expand Down
Loading