diff --git a/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap b/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap
index 25480a4633..0fae9a3449 100644
--- a/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap
+++ b/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap
@@ -17484,6 +17484,28 @@ render to an element under \`document.body\`.",
"optional": true,
"type": "HTMLElement",
},
+ {
+ "defaultValue": "'center'",
+ "description": "Controls the vertical positioning of the modal.
+
+- \`center\` (default) - Modal is vertically centered in viewport and re-centers
+ when content height changes. Use for dialogs with static, predictable content.
+
+- \`top\` - Modal anchors at fixed distance and grows downward
+ as content expands. Use when content changes dynamically to prevent disruptive
+ vertical repositioning that causes users to lose focus.",
+ "inlineType": {
+ "name": "ModalProps.Position",
+ "type": "union",
+ "values": [
+ "center",
+ "top",
+ ],
+ },
+ "name": "position",
+ "optional": true,
+ "type": "string",
+ },
{
"description": "Use this property when \`getModalRoot\` is used to clean up the modal root
element after a user closes the dialog. The function receives the return value
diff --git a/src/modal/__tests__/modal.test.tsx b/src/modal/__tests__/modal.test.tsx
index d36bb7ab3d..c70fba989f 100644
--- a/src/modal/__tests__/modal.test.tsx
+++ b/src/modal/__tests__/modal.test.tsx
@@ -119,6 +119,15 @@ describe('Modal component', () => {
});
});
+ describe('position property', () => {
+ it('displays correct position', () => {
+ (['center', 'top'] as ModalProps.Position[]).forEach(position => {
+ const wrapper = renderModal({ position });
+ expect(wrapper.findDialog().getElement()).toHaveClass(styles[position]);
+ });
+ });
+ });
+
describe('dismiss on click', () => {
it('closes the dialog when clicked on the overlay section of the container', () => {
const onDismissSpy = jest.fn();
diff --git a/src/modal/index.tsx b/src/modal/index.tsx
index c2b6982958..4195ce68c8 100644
--- a/src/modal/index.tsx
+++ b/src/modal/index.tsx
@@ -70,7 +70,7 @@ function ModalWithAnalyticsFunnel({
);
}
-export default function Modal({ size = 'medium', ...props }: ModalProps) {
+export default function Modal({ size = 'medium', position = 'center', ...props }: ModalProps) {
const { isInFunnel } = useFunnel();
const analyticsMetadata = getAnalyticsMetadataProps(props as BasePropsWithAnalyticsMetadata);
const baseComponentProps = useBaseComponent(
@@ -78,6 +78,7 @@ export default function Modal({ size = 'medium', ...props }: ModalProps) {
{
props: {
size,
+ position,
disableContentPaddings: props.disableContentPaddings,
flowType: analyticsMetadata.flowType,
},
@@ -95,12 +96,21 @@ export default function Modal({ size = 'medium', ...props }: ModalProps) {
analyticsMetadata={analyticsMetadata}
baseComponentProps={baseComponentProps}
size={size}
+ position={position}
{...props}
/>
);
}
- return ;
+ return (
+
+ );
}
applyDisplayName(Modal, 'Modal');
diff --git a/src/modal/interfaces.ts b/src/modal/interfaces.ts
index de33a935b9..99ff019aaf 100644
--- a/src/modal/interfaces.ts
+++ b/src/modal/interfaces.ts
@@ -30,6 +30,17 @@ export interface ModalProps extends BaseComponentProps, BaseModalProps {
* `small` (320px), `medium` (600px), `large` (820px), `x-large` (1024px), `xx-large` (1280px).
*/
size?: ModalProps.Size;
+ /**
+ * Controls the vertical positioning of the modal.
+ *
+ * - `center` (default) - Modal is vertically centered in viewport and re-centers
+ * when content height changes. Use for dialogs with static, predictable content.
+ *
+ * - `top` - Modal anchors at fixed distance and grows downward
+ * as content expands. Use when content changes dynamically to prevent disruptive
+ * vertical repositioning that causes users to lose focus.
+ */
+ position?: ModalProps.Position;
/**
* Determines whether the modal is displayed on the screen. Modals are hidden by default.
* Set this property to `true` to show them.
@@ -82,6 +93,7 @@ export interface ModalProps extends BaseComponentProps, BaseModalProps {
export namespace ModalProps {
export type Size = 'small' | 'medium' | 'large' | 'x-large' | 'xx-large' | 'max';
+ export type Position = 'center' | 'top';
export interface DismissDetail {
reason: string;
diff --git a/src/modal/internal.tsx b/src/modal/internal.tsx
index 79e9e88c31..71877e9c4f 100644
--- a/src/modal/internal.tsx
+++ b/src/modal/internal.tsx
@@ -94,6 +94,7 @@ function PortaledModal({
children,
footer,
disableContentPaddings,
+ position = 'center',
onButtonClick = () => {},
onDismiss,
__internalRootRef,
@@ -247,7 +248,12 @@ function PortaledModal({
style={footerHeight ? { scrollPaddingBottom: footerHeight } : undefined}
data-awsui-referrer-id={subStepRef.current?.id || referrerId}
>
-
+