diff --git a/pages/container/style-api-v2.page.tsx b/pages/container/style-api-v2.page.tsx
new file mode 100644
index 00000000000..71615e653b8
--- /dev/null
+++ b/pages/container/style-api-v2.page.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 { Box, ColumnLayout, Container, ContainerProps, Header } from '~components';
+
+import { SimplePage } from '../app/templates';
+import image169 from './images/16-9.png';
+
+import styles from './style-api-v2.scss';
+
+function StyledContainer(props: ContainerProps) {
+ return (
+
+ );
+}
+
+function Permutation({ label, children }: { label: string; children: React.ReactNode }) {
+ return (
+
+
+ {label}
+
+ {children}
+
+ );
+}
+
+export default function () {
+ return (
+
+
+
+ Header} footer="Footer">
+ Content
+
+
+
+ Header}
+ media={{ content:
, position: 'top', height: 120 }}
+ >
+ Content
+
+
+
+ First} footer="First footer">
+ Content
+
+ Second} footer="Second footer">
+ Content
+
+
+
+ Header}>Content
+
+
+ Content
+
+
+
+ Header filled by its own element
+
+ }
+ >
+ Content
+
+
+
+
+ );
+}
diff --git a/pages/container/style-api-v2.scss b/pages/container/style-api-v2.scss
new file mode 100644
index 00000000000..111168020ba
--- /dev/null
+++ b/pages/container/style-api-v2.scss
@@ -0,0 +1,36 @@
+/*
+ Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ SPDX-License-Identifier: Apache-2.0
+*/
+
+@use '~components/styling/container';
+@use '../style-api/palette' as palette;
+
+.container {
+ @include container.root(
+ $background: palette.$surface,
+ $border-color: palette.$border-accent,
+ $border-width: 2px,
+ $border-style: solid,
+ $box-shadow: 0 4px 12px palette.$subtle-active,
+ $border-radius: 24px
+ );
+}
+
+// Only the block-end side of the header border is visible: it is the divider between the header and the content.
+.header {
+ @include container.header(
+ $background: palette.$genai-surface,
+ $border-color: palette.$genai,
+ $border-block-width: 0 2px
+ );
+}
+
+// Only the block-start side of the footer border is visible: it is the divider between the content and the footer.
+.footer {
+ @include container.footer(
+ $background: palette.$info-surface,
+ $border-color: palette.$info,
+ $border-block-width: 3px 0
+ );
+}
diff --git a/src/__tests__/snapshot-tests/__snapshots__/style-docs.test.ts.snap b/src/__tests__/snapshot-tests/__snapshots__/style-docs.test.ts.snap
index 53c0c16de39..4c060129cf1 100644
--- a/src/__tests__/snapshot-tests/__snapshots__/style-docs.test.ts.snap
+++ b/src/__tests__/snapshot-tests/__snapshots__/style-docs.test.ts.snap
@@ -109,6 +109,50 @@ exports[`Style API docs definition for checkbox matches the snapshot: checkbox 1
}
`;
+exports[`Style API docs definition for container matches the snapshot: container 1`] = `
+{
+ "slots": [
+ {
+ "name": "root",
+ "properties": [
+ "background",
+ "border",
+ "box-shadow",
+ ],
+ "propertyDescriptions": {},
+ "tokenDescriptions": {
+ "border-radius": "Also rounds the header, footer and media corners. Adjoining corners of stacked containers stay square.",
+ },
+ "tokens": [
+ "border-radius",
+ ],
+ },
+ {
+ "description": "Rendered when \`header\` is set. The block-end border is the divider between the header and the content.",
+ "name": "header",
+ "properties": [
+ "background",
+ "border",
+ ],
+ "propertyDescriptions": {},
+ "tokenDescriptions": {},
+ "tokens": [],
+ },
+ {
+ "description": "Rendered when \`footer\` is set. The block-start border is the divider between the content and the footer.",
+ "name": "footer",
+ "properties": [
+ "background",
+ "border",
+ ],
+ "propertyDescriptions": {},
+ "tokenDescriptions": {},
+ "tokens": [],
+ },
+ ],
+}
+`;
+
exports[`Style API docs definition for dropdown matches the snapshot: dropdown 1`] = `
{
"slots": [
@@ -274,6 +318,7 @@ exports[`Style API docs list of components with a Style API doc matches the snap
"badge",
"button",
"checkbox",
+ "container",
"dropdown",
"input",
"link",
diff --git a/src/container/__tests__/style-api.test.tsx b/src/container/__tests__/style-api.test.tsx
new file mode 100644
index 00000000000..ff866ee716b
--- /dev/null
+++ b/src/container/__tests__/style-api.test.tsx
@@ -0,0 +1,40 @@
+// 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 Container, { ContainerProps } from '../../../lib/components/container';
+import createWrapper from '../../../lib/components/test-utils/dom';
+
+function renderContainer(props: ContainerProps & { styleClassNames?: Record } = {}) {
+ const { container } = render(Content);
+ return { container, wrapper: createWrapper(container).findContainer()! };
+}
+
+const styleClassNames = { root: 'a', header: 'b', footer: 'c', unknown: 'd' };
+
+describe('Container Style API v2', () => {
+ test('applies each slot class to its element and ignores unknown keys', () => {
+ const { container, wrapper } = renderContainer({ header: 'Header', footer: 'Footer', styleClassNames });
+ expect(wrapper.getElement()).toHaveClass('a');
+ expect(wrapper.findHeader()!.getElement()).toHaveClass('b');
+ expect(wrapper.findFooter()!.getElement()).toHaveClass('c');
+ expect(container.querySelectorAll('.a, .b, .c')).toHaveLength(3);
+ expect(container.querySelector('.d')).toBeNull();
+ });
+
+ test('applies the root class in the stacked variant and with media', () => {
+ const { container, wrapper } = renderContainer({
+ variant: 'stacked',
+ media: { content:
},
+ styleClassNames,
+ });
+ expect(wrapper.getElement()).toHaveClass('a');
+ expect(container.querySelectorAll('.a')).toHaveLength(1);
+ });
+
+ test('does not leak the styleClassNames prop to the DOM', () => {
+ const { container } = renderContainer({ header: 'Header', footer: 'Footer', styleClassNames });
+ expect(container.querySelector('[styleClassNames]')).toBeNull();
+ });
+});
diff --git a/src/container/internal.tsx b/src/container/internal.tsx
index e5e8c6a6aa4..c7ac8f4bf01 100644
--- a/src/container/internal.tsx
+++ b/src/container/internal.tsx
@@ -51,6 +51,15 @@ export interface InternalContainerProps extends Omit,
* is necessary for components that use container as a root node internally so that their test-utils wrapper is not detached.
*/
__contentKey?: string;
+
+ styleClassNames?: StyleClassNames;
+}
+
+// Style API v2
+export interface StyleClassNames {
+ root?: string;
+ header?: string;
+ footer?: string;
}
export const InternalContainerAsSubstep = React.forwardRef(
@@ -93,6 +102,7 @@ const InternalContainer = React.forwardRef(function InternalContainer(
__funnelSubStepProps,
__subStepRef,
__contentKey,
+ styleClassNames,
...restProps
}: InternalContainerProps,
ref: React.Ref
@@ -158,6 +168,7 @@ const InternalContainer = React.forwardRef(function InternalContainer(
{...baseProps}
{...__funnelSubStepProps}
className={clsx(
+ styleClassNames?.root,
baseProps.className,
styles.root,
styles[`variant-${variant}`],
@@ -201,6 +212,7 @@ const InternalContainer = React.forwardRef(function InternalContainer(