diff --git a/build-tools/utils/pluralize.js b/build-tools/utils/pluralize.js
index 0a6439d9c0..4cf00492c3 100644
--- a/build-tools/utils/pluralize.js
+++ b/build-tools/utils/pluralize.js
@@ -22,6 +22,7 @@ const pluralizationMap = {
CodeEditor: 'CodeEditors',
CollectionPreferences: 'CollectionPreferences',
ColumnLayout: 'ColumnLayouts',
+ ComparisonTable: 'ComparisonTables',
Container: 'Containers',
ContentLayout: 'ContentLayouts',
CopyToClipboard: 'CopyToClipboards',
diff --git a/pages/comparison-table/simple.page.tsx b/pages/comparison-table/simple.page.tsx
new file mode 100644
index 0000000000..00e050a71b
--- /dev/null
+++ b/pages/comparison-table/simple.page.tsx
@@ -0,0 +1,79 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+import * as React from 'react';
+
+import Box from '~components/box';
+import ComparisonTable, { ComparisonTableProps } from '~components/comparison-table';
+import SpaceBetween from '~components/space-between';
+import StatusIndicator from '~components/status-indicator';
+
+import ScreenshotArea from '../utils/screenshot-area';
+
+const attributes: ComparisonTableProps['attributes'] = [
+ { id: 'engine', label: 'Database engine' },
+ { id: 'vcpu', label: 'vCPU' },
+ { id: 'memory', label: 'Memory' },
+ {
+ id: 'multiAz',
+ label: 'Multi-AZ',
+ render: value => (
+ {value ? 'Enabled' : 'Disabled'}
+ ),
+ },
+ { id: 'storage', label: 'Max storage' },
+];
+
+const entities: ComparisonTableProps['entities'] = [
+ {
+ id: 'db-r6g',
+ title: 'db.r6g.large',
+ data: { engine: 'MySQL', vcpu: 2, memory: '16 GiB', multiAz: true, storage: '64 TiB' },
+ },
+ {
+ id: 'db-r6g-xl',
+ title: 'db.r6g.xlarge',
+ data: { engine: 'MySQL', vcpu: 4, memory: '32 GiB', multiAz: true, storage: '64 TiB' },
+ },
+ {
+ id: 'db-r6g-2xl',
+ title: 'db.r6g.2xlarge',
+ data: { engine: 'MySQL', vcpu: 8, memory: '64 GiB', multiAz: false, storage: '64 TiB' },
+ },
+];
+
+export default function ComparisonTablePage() {
+ const [highlight, setHighlight] = React.useState(true);
+ const [sticky, setSticky] = React.useState(true);
+
+ return (
+ <>
+
Comparison table (WIP)
+
+
+
+
+
+
+
+
+
+
+ Empty state
+
+
+ >
+ );
+}
diff --git a/src/__tests__/required-props-for-components.ts b/src/__tests__/required-props-for-components.ts
index 3430c62d2d..933fa8c139 100644
--- a/src/__tests__/required-props-for-components.ts
+++ b/src/__tests__/required-props-for-components.ts
@@ -6,6 +6,7 @@ export { defaultSplitPanelContextProps } from '../split-panel/__tests__/helpers'
const defaultProps: Record> = {
tabs: { tabs: [] },
table: { columnDefinitions: [] },
+ 'comparison-table': { attributes: [], entities: [] },
cards: { cardDefinition: {} },
autosuggest: { options: [], enteredPrefix: '' },
'anchor-navigation': { anchors: [] },
diff --git a/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap b/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap
index b54c1b56ab..200f12e5de 100644
--- a/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap
+++ b/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap
@@ -10214,6 +10214,115 @@ and the maximum number of columns as defined by the \`columns\` property.",
}
`;
+exports[`Components definition for comparison-table matches the snapshot: comparison-table 1`] = `
+{
+ "dashCaseName": "comparison-table",
+ "description": "ComparisonTable renders a side-by-side comparison of two or more entities: attributes are
+rendered as rows, entities as columns, with a sticky attribute column and optional
+highlighting of rows whose values differ across entities.
+
+This is a work-in-progress (v0) component built as a thin, opt-in composition over the
+existing Table component.",
+ "events": [],
+ "functions": [],
+ "name": "ComparisonTable",
+ "properties": [
+ {
+ "description": "Provides an accessible name for the underlying table, announced by assistive technology.",
+ "name": "ariaLabel",
+ "optional": true,
+ "type": "string",
+ },
+ {
+ "defaultValue": "''",
+ "description": "Header text rendered above the sticky attribute (first) column.
+Defaults to an empty string.",
+ "name": "attributeColumnHeader",
+ "optional": true,
+ "type": "string",
+ },
+ {
+ "description": "The attributes to compare across entities. Each attribute is rendered as a **row**.
+
+The order of this array is the order the rows appear in. Use the \`render\` function on an
+attribute to customize how the value for each entity is displayed.",
+ "name": "attributes",
+ "optional": false,
+ "type": "ReadonlyArray",
+ },
+ {
+ "deprecatedTag": "Custom CSS is not supported. For testing and other use cases, use [data attributes](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes).",
+ "description": "Adds the specified classes to the root element of the component.",
+ "name": "className",
+ "optional": true,
+ "type": "string",
+ },
+ {
+ "description": "The entities being compared. Each entity is rendered as a **column** next to the sticky
+attribute column.
+
+Every entity exposes its per-attribute values through the \`data\` map, keyed by attribute \`id\`.",
+ "name": "entities",
+ "optional": false,
+ "type": "ReadonlyArray",
+ },
+ {
+ "defaultValue": "false",
+ "description": "When \`true\`, attribute rows whose values are not identical across all entities are visually
+emphasized so differences stand out.
+
+Equality is determined with \`Object.is\` against the raw \`entity.data[attribute.id]\` values.
+Rows with a custom \`render\` are still compared using their raw \`data\` values, so keep the
+\`data\` values comparable (primitives) when relying on this feature.",
+ "name": "highlightDifferences",
+ "optional": true,
+ "type": "boolean",
+ },
+ {
+ "deprecatedTag": "The usage of the \`id\` attribute is reserved for internal use cases. For testing and other use cases,
+use [data attributes](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes). If you must
+use the \`id\` attribute, consider setting it on a parent element instead.",
+ "description": "Adds the specified ID to the root element of the component.",
+ "name": "id",
+ "optional": true,
+ "type": "string",
+ },
+ {
+ "defaultValue": "true",
+ "description": "Whether the attribute (first) column is pinned to the inline-start edge while the entity
+columns scroll horizontally. Defaults to \`true\`.",
+ "name": "stickyAttributeColumn",
+ "optional": true,
+ "type": "boolean",
+ },
+ {
+ "defaultValue": "'container'",
+ "description": "The visual variant of the comparison table.
+
+- \`"container"\` (default) — renders inside a bordered container.
+- \`"embedded"\` — removes the outer container border for embedding in another surface.
+- \`"borderless"\` — removes borders entirely.
+- \`"stacked"\` — for use in a vertically stacked set of containers.",
+ "inlineType": {
+ "name": "ComparisonTableProps.Variant",
+ "type": "union",
+ "values": [
+ "container",
+ "embedded",
+ "stacked",
+ "borderless",
+ ],
+ },
+ "name": "variant",
+ "optional": true,
+ "type": "string",
+ },
+ ],
+ "regions": [],
+ "releaseStatus": "stable",
+}
+`;
+
exports[`Components definition for container matches the snapshot: container 1`] = `
{
"dashCaseName": "container",
@@ -39171,6 +39280,53 @@ Note that, despite its typings, this may return null for group items since group
],
"name": "ColumnLayoutWrapper",
},
+ {
+ "methods": [
+ {
+ "description": "Returns all attribute (row header) label elements.",
+ "name": "findAttributeLabels",
+ "parameters": [],
+ "returnType": {
+ "isNullable": false,
+ "name": "Array",
+ "typeArguments": [
+ {
+ "name": "ElementWrapper",
+ },
+ ],
+ },
+ },
+ {
+ "description": "Returns all cells that are highlighted because their attribute row differs across entities.",
+ "name": "findHighlightedCells",
+ "parameters": [],
+ "returnType": {
+ "isNullable": false,
+ "name": "Array",
+ "typeArguments": [
+ {
+ "name": "ElementWrapper",
+ },
+ ],
+ },
+ },
+ {
+ "description": "Returns all rendered rows of the underlying table (one per attribute).",
+ "name": "findRows",
+ "parameters": [],
+ "returnType": {
+ "isNullable": false,
+ "name": "Array",
+ "typeArguments": [
+ {
+ "name": "ElementWrapper",
+ },
+ ],
+ },
+ },
+ ],
+ "name": "ComparisonTableWrapper",
+ },
{
"methods": [
{
@@ -50181,6 +50337,53 @@ Note that, despite its typings, this may return null for group items since group
],
"name": "ColumnLayoutWrapper",
},
+ {
+ "methods": [
+ {
+ "description": "Returns all attribute (row header) label elements.",
+ "name": "findAttributeLabels",
+ "parameters": [],
+ "returnType": {
+ "isNullable": false,
+ "name": "MultiElementWrapper",
+ "typeArguments": [
+ {
+ "name": "ElementWrapper",
+ },
+ ],
+ },
+ },
+ {
+ "description": "Returns all cells that are highlighted because their attribute row differs across entities.",
+ "name": "findHighlightedCells",
+ "parameters": [],
+ "returnType": {
+ "isNullable": false,
+ "name": "MultiElementWrapper",
+ "typeArguments": [
+ {
+ "name": "ElementWrapper",
+ },
+ ],
+ },
+ },
+ {
+ "description": "Returns all rendered rows of the underlying table (one per attribute).",
+ "name": "findRows",
+ "parameters": [],
+ "returnType": {
+ "isNullable": false,
+ "name": "MultiElementWrapper",
+ "typeArguments": [
+ {
+ "name": "ElementWrapper",
+ },
+ ],
+ },
+ },
+ ],
+ "name": "ComparisonTableWrapper",
+ },
{
"methods": [
{
diff --git a/src/__tests__/snapshot-tests/__snapshots__/test-utils-selectors.test.tsx.snap b/src/__tests__/snapshot-tests/__snapshots__/test-utils-selectors.test.tsx.snap
index 4b1af42bc5..0e2e82e77f 100644
--- a/src/__tests__/snapshot-tests/__snapshots__/test-utils-selectors.test.tsx.snap
+++ b/src/__tests__/snapshot-tests/__snapshots__/test-utils-selectors.test.tsx.snap
@@ -191,6 +191,11 @@ exports[`test-utils selectors 1`] = `
"awsui_column-layout_vvxn7",
"awsui_item_zqq3x",
],
+ "comparison-table": [
+ "awsui_attribute-label_gfu3l",
+ "awsui_cell-highlighted_gfu3l",
+ "awsui_root_gfu3l",
+ ],
"container": [
"awsui_content-inner_1mwlm",
"awsui_content-wrapper_14iqq",
diff --git a/src/comparison-table/__tests__/comparison-table.test.tsx b/src/comparison-table/__tests__/comparison-table.test.tsx
new file mode 100644
index 0000000000..1776c054bf
--- /dev/null
+++ b/src/comparison-table/__tests__/comparison-table.test.tsx
@@ -0,0 +1,78 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+import React from 'react';
+import { render } from '@testing-library/react';
+
+import ComparisonTable, { ComparisonTableProps } from '../../../lib/components/comparison-table';
+import createWrapper from '../../../lib/components/test-utils/dom';
+
+const attributes: ComparisonTableProps['attributes'] = [
+ { id: 'engine', label: 'Engine' },
+ { id: 'vcpu', label: 'vCPU' },
+];
+
+const entities: ComparisonTableProps['entities'] = [
+ { id: 'a', title: 'Option A', data: { engine: 'MySQL', vcpu: 2 } },
+ { id: 'b', title: 'Option B', data: { engine: 'MySQL', vcpu: 4 } },
+];
+
+function renderComparisonTable(jsx: React.ReactElement) {
+ const { container } = render(jsx);
+ return createWrapper(container).findComparisonTable()!;
+}
+
+describe('ComparisonTable', () => {
+ test('renders and is findable via test-utils', () => {
+ const wrapper = renderComparisonTable();
+ expect(wrapper).not.toBeNull();
+ });
+
+ test('renders one row per attribute (attributes as rows)', () => {
+ const wrapper = renderComparisonTable();
+ expect(wrapper.findRows()).toHaveLength(attributes.length);
+ });
+
+ test('renders one column header per entity plus the attribute column', () => {
+ const wrapper = renderComparisonTable();
+ // Attribute column header + one per entity.
+ const headers = wrapper.getElement().querySelectorAll('thead th');
+ expect(headers.length).toBe(entities.length + 1);
+ });
+
+ test('renders attribute labels in the sticky attribute column', () => {
+ const wrapper = renderComparisonTable();
+ const labels = wrapper.findAttributeLabels().map(l => l.getElement().textContent);
+ expect(labels).toEqual(['Engine', 'vCPU']);
+ });
+
+ test('highlightDifferences emphasizes only differing rows', () => {
+ const wrapper = renderComparisonTable(
+
+ );
+ // "engine" is identical (MySQL/MySQL); "vcpu" differs (2 vs 4) => one differing row across 2 entity cells.
+ expect(wrapper.findHighlightedCells()).toHaveLength(2);
+ });
+
+ test('does not highlight anything when highlightDifferences is false', () => {
+ const wrapper = renderComparisonTable(
+
+ );
+ expect(wrapper.findHighlightedCells()).toHaveLength(0);
+ });
+
+ test('uses the render function to customize entity values', () => {
+ const wrapper = renderComparisonTable(
+ {`[${value}]`} }]}
+ entities={entities}
+ />
+ );
+ expect(wrapper.getElement().textContent).toContain('[MySQL]');
+ });
+
+ test('renders without crashing when there are no attributes or entities', () => {
+ const wrapper = renderComparisonTable();
+ expect(wrapper).not.toBeNull();
+ expect(wrapper.findRows()).toHaveLength(0);
+ });
+});
diff --git a/src/comparison-table/index.tsx b/src/comparison-table/index.tsx
new file mode 100644
index 0000000000..c48b022583
--- /dev/null
+++ b/src/comparison-table/index.tsx
@@ -0,0 +1,53 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+'use client';
+import React from 'react';
+
+import { getBaseProps } from '../internal/base-component';
+import useBaseComponent from '../internal/hooks/use-base-component';
+import { applyDisplayName } from '../internal/utils/apply-display-name';
+import { ComparisonTableProps } from './interfaces';
+import InternalComparisonTable from './internal';
+
+export { ComparisonTableProps };
+
+/**
+ * ComparisonTable renders a side-by-side comparison of two or more entities: attributes are
+ * rendered as rows, entities as columns, with a sticky attribute column and optional
+ * highlighting of rows whose values differ across entities.
+ *
+ * This is a work-in-progress (v0) component built as a thin, opt-in composition over the
+ * existing Table component.
+ */
+export default function ComparisonTable({
+ attributes,
+ entities,
+ attributeColumnHeader = '',
+ stickyAttributeColumn = true,
+ highlightDifferences = false,
+ variant = 'container',
+ ariaLabel,
+ ...rest
+}: ComparisonTableProps) {
+ const baseComponentProps = useBaseComponent('ComparisonTable', {
+ props: { stickyAttributeColumn, highlightDifferences, variant },
+ metadata: { attributesCount: attributes.length, entitiesCount: entities.length },
+ });
+ const baseProps = getBaseProps(rest);
+
+ return (
+
+ );
+}
+
+applyDisplayName(ComparisonTable, 'ComparisonTable');
diff --git a/src/comparison-table/interfaces.ts b/src/comparison-table/interfaces.ts
new file mode 100644
index 0000000000..8b6e7eafeb
--- /dev/null
+++ b/src/comparison-table/interfaces.ts
@@ -0,0 +1,100 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+import React from 'react';
+
+import { BaseComponentProps } from '../types/base-component';
+
+export interface ComparisonTableProps extends BaseComponentProps {
+ /**
+ * The attributes to compare across entities. Each attribute is rendered as a **row**.
+ *
+ * The order of this array is the order the rows appear in. Use the `render` function on an
+ * attribute to customize how the value for each entity is displayed.
+ */
+ attributes: ReadonlyArray;
+
+ /**
+ * The entities being compared. Each entity is rendered as a **column** next to the sticky
+ * attribute column.
+ *
+ * Every entity exposes its per-attribute values through the `data` map, keyed by attribute `id`.
+ */
+ entities: ReadonlyArray;
+
+ /**
+ * Header text rendered above the sticky attribute (first) column.
+ * Defaults to an empty string.
+ */
+ attributeColumnHeader?: string;
+
+ /**
+ * Whether the attribute (first) column is pinned to the inline-start edge while the entity
+ * columns scroll horizontally. Defaults to `true`.
+ */
+ stickyAttributeColumn?: boolean;
+
+ /**
+ * When `true`, attribute rows whose values are not identical across all entities are visually
+ * emphasized so differences stand out.
+ *
+ * Equality is determined with `Object.is` against the raw `entity.data[attribute.id]` values.
+ * Rows with a custom `render` are still compared using their raw `data` values, so keep the
+ * `data` values comparable (primitives) when relying on this feature.
+ */
+ highlightDifferences?: boolean;
+
+ /**
+ * Provides an accessible name for the underlying table, announced by assistive technology.
+ */
+ ariaLabel?: string;
+
+ /**
+ * The visual variant of the comparison table.
+ *
+ * - `"container"` (default) — renders inside a bordered container.
+ * - `"embedded"` — removes the outer container border for embedding in another surface.
+ * - `"borderless"` — removes borders entirely.
+ * - `"stacked"` — for use in a vertically stacked set of containers.
+ */
+ variant?: ComparisonTableProps.Variant;
+}
+
+export namespace ComparisonTableProps {
+ export type Variant = 'container' | 'embedded' | 'borderless' | 'stacked';
+
+ export interface Attribute {
+ /**
+ * Unique identifier for the attribute. Used to look up each entity's value via
+ * `entity.data[id]` and as the React key for the row.
+ */
+ id: string;
+
+ /**
+ * The label rendered in the sticky attribute column for this row.
+ */
+ label: React.ReactNode;
+
+ /**
+ * Optional custom renderer for an entity's value for this attribute. Receives the raw value
+ * (`entity.data[id]`) and the full entity. When omitted, the raw value is rendered as-is.
+ */
+ render?: (value: React.ReactNode, entity: ComparisonTableProps.Entity) => React.ReactNode;
+ }
+
+ export interface Entity {
+ /**
+ * Unique identifier for the entity. Used as the column id and React key.
+ */
+ id: string;
+
+ /**
+ * The column header for this entity (for example, the resource name).
+ */
+ title: React.ReactNode;
+
+ /**
+ * The entity's values, keyed by attribute `id`. Missing keys render as a placeholder.
+ */
+ data: Record;
+ }
+}
diff --git a/src/comparison-table/internal.tsx b/src/comparison-table/internal.tsx
new file mode 100644
index 0000000000..c13f3423f2
--- /dev/null
+++ b/src/comparison-table/internal.tsx
@@ -0,0 +1,103 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+import React from 'react';
+import clsx from 'clsx';
+
+import { getBaseProps } from '../internal/base-component';
+import { InternalBaseComponentProps } from '../internal/hooks/use-base-component';
+import { TableProps } from '../table/interfaces';
+import InternalTable from '../table/internal';
+import { ComparisonTableProps } from './interfaces';
+
+import styles from './styles.css.js';
+
+type InternalComparisonTableProps = ComparisonTableProps & InternalBaseComponentProps;
+
+const ATTRIBUTE_COLUMN_ID = 'awsui-comparison-table-attribute';
+
+// Computes the set of attribute ids whose values differ across the compared entities.
+// Comparison uses `Object.is` against the raw per-entity value, so it is reliable for
+// primitive values. Deep object comparison is intentionally out of scope for v0.
+function getDifferingAttributeIds(
+ attributes: ReadonlyArray,
+ entities: ReadonlyArray
+): Set {
+ const differing = new Set();
+ if (entities.length < 2) {
+ return differing;
+ }
+ for (const attribute of attributes) {
+ const [firstEntity, ...restEntities] = entities;
+ const firstValue = firstEntity.data?.[attribute.id];
+ const allEqual = restEntities.every(entity => Object.is(entity.data?.[attribute.id], firstValue));
+ if (!allEqual) {
+ differing.add(attribute.id);
+ }
+ }
+ return differing;
+}
+
+export default function InternalComparisonTable({
+ attributes,
+ entities,
+ attributeColumnHeader = '',
+ stickyAttributeColumn = true,
+ highlightDifferences = false,
+ ariaLabel,
+ variant = 'container',
+ __internalRootRef,
+ ...rest
+}: InternalComparisonTableProps) {
+ const baseProps = getBaseProps(rest);
+
+ const differingAttributeIds = highlightDifferences
+ ? getDifferingAttributeIds(attributes, entities)
+ : new Set();
+
+ const columnDefinitions: ReadonlyArray> = [
+ {
+ id: ATTRIBUTE_COLUMN_ID,
+ header: attributeColumnHeader,
+ isRowHeader: true,
+ cell: attribute => {attribute.label},
+ },
+ ...entities.map(
+ (entity): TableProps.ColumnDefinition => ({
+ id: entity.id,
+ header: entity.title,
+ cell: attribute => {
+ const rawValue = entity.data?.[attribute.id];
+ const value = attribute.render ? attribute.render(rawValue, entity) : rawValue;
+ const isDifferent = differingAttributeIds.has(attribute.id);
+ return (
+
+ {value === undefined || value === null ? '-' : value}
+
+ );
+ },
+ })
+ ),
+ ];
+
+ // `__internalRootRef` is spread (not written inline) so it bypasses the excess-property check
+ // against the public TableProps, while still landing on the underlying table root. This carries
+ // the ComparisonTable base-component metadata onto the rendered root element.
+ const internalBaseProps: InternalBaseComponentProps = { __internalRootRef };
+
+ const tableProps: Parameters>[0] = {
+ ...baseProps,
+ ...internalBaseProps,
+ className: clsx(baseProps.className, styles.root),
+ variant,
+ items: [...attributes],
+ selectedItems: [],
+ firstIndex: 1,
+ cellVerticalAlign: 'top',
+ columnDefinitions,
+ trackBy: 'id',
+ stickyColumns: stickyAttributeColumn ? { first: 1 } : undefined,
+ ariaLabels: ariaLabel ? { tableLabel: ariaLabel } : undefined,
+ };
+
+ return ;
+}
diff --git a/src/comparison-table/styles.scss b/src/comparison-table/styles.scss
new file mode 100644
index 0000000000..5c9c73b33a
--- /dev/null
+++ b/src/comparison-table/styles.scss
@@ -0,0 +1,36 @@
+/*
+ Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ SPDX-License-Identifier: Apache-2.0
+*/
+
+@use '../internal/styles' as styles;
+@use '../internal/styles/tokens' as awsui;
+
+.root {
+ // Marker class applied to the underlying Table root so test-utils and consumers can target
+ // the comparison table specifically. Visual styling is inherited from Table.
+ inline-size: 100%;
+}
+
+.attribute-label {
+ font-weight: styles.$font-weight-bold;
+ color: awsui.$color-text-body-default;
+}
+
+.cell {
+ color: awsui.$color-text-body-default;
+}
+
+// Emphasis applied to a cell when its attribute row differs across entities.
+.cell-highlighted {
+ display: inline-block;
+ padding-block: awsui.$space-xxxs;
+ padding-inline: awsui.$space-xxs;
+ border-start-start-radius: awsui.$border-radius-badge;
+ border-start-end-radius: awsui.$border-radius-badge;
+ border-end-start-radius: awsui.$border-radius-badge;
+ border-end-end-radius: awsui.$border-radius-badge;
+ background-color: awsui.$color-background-status-warning;
+ color: awsui.$color-text-status-warning;
+ font-weight: styles.$font-weight-bold;
+}
diff --git a/src/test-utils/dom/comparison-table/index.ts b/src/test-utils/dom/comparison-table/index.ts
new file mode 100644
index 0000000000..a4a6076653
--- /dev/null
+++ b/src/test-utils/dom/comparison-table/index.ts
@@ -0,0 +1,25 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+import { ComponentWrapper, ElementWrapper } from '@cloudscape-design/test-utils-core/dom';
+
+import styles from '../../../comparison-table/styles.selectors.js';
+import tableStyles from '../../../table/styles.selectors.js';
+
+export default class ComparisonTableWrapper extends ComponentWrapper {
+ static rootSelector: string = styles.root;
+
+ /** Returns all attribute (row header) label elements. */
+ findAttributeLabels(): Array {
+ return this.findAllByClassName(styles['attribute-label']);
+ }
+
+ /** Returns all cells that are highlighted because their attribute row differs across entities. */
+ findHighlightedCells(): Array {
+ return this.findAllByClassName(styles['cell-highlighted']);
+ }
+
+ /** Returns all rendered rows of the underlying table (one per attribute). */
+ findRows(): Array {
+ return this.findAllByClassName(tableStyles.row);
+ }
+}