diff --git a/pages/wizard/custom-primary-actions.page.tsx b/pages/wizard/custom-primary-actions.page.tsx
new file mode 100644
index 0000000000..01810643fb
--- /dev/null
+++ b/pages/wizard/custom-primary-actions.page.tsx
@@ -0,0 +1,96 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+import React, { useState } from 'react';
+
+import { SpaceBetween } from '~components';
+import Button from '~components/button';
+import Container from '~components/container';
+import Header from '~components/header';
+import Wizard, { WizardProps } from '~components/wizard';
+
+import { i18nStrings } from './common';
+
+const steps: WizardProps.Step[] = [
+ {
+ title: 'Step 1',
+ content: (
+ <>
+ Step 1, substep one}>
+ Step 1, substep two}>
+ >
+ ),
+ },
+ {
+ title: 'Step 2',
+ content: (
+ <>
+ Step 2, substep one}>
+ Step 2, substep two}>
+ >
+ ),
+ isOptional: true,
+ },
+ {
+ title: 'Step 3',
+ content: (
+ <>
+ Step 3, substep one}>
+ Step 3, substep two}>
+ >
+ ),
+ },
+];
+
+export default function WizardPage() {
+ const [activeStepIndex, setActiveStepIndex] = useState(0);
+
+ const onNext = () => {
+ if (activeStepIndex >= steps.length) {
+ return;
+ }
+ setActiveStepIndex(activeStepIndex + 1);
+ };
+
+ const onPrevious = () => {
+ if (activeStepIndex <= 0) {
+ return;
+ }
+ setActiveStepIndex(activeStepIndex - 1);
+ };
+
+ const onFinish = () => {
+ alert('Finish');
+ };
+
+ const customPrimaryActions = (
+
+ {activeStepIndex > 0 && (
+
+ )}
+ {activeStepIndex < steps.length - 1 && (
+
+ )}
+ {activeStepIndex === steps.length - 1 && (
+
+ )}
+
+ );
+
+ return (
+ setActiveStepIndex(e.detail.requestedStepIndex)}
+ secondaryActions={activeStepIndex === 2 ? : null}
+ customPrimaryActions={customPrimaryActions}
+ />
+ );
+}
diff --git a/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap b/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap
index f4a10fff93..93e3d26acd 100644
--- a/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap
+++ b/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap
@@ -28027,6 +28027,14 @@ Use this if you need to wait for a response from the server before the user can
},
],
"regions": [
+ {
+ "description": "Specifies right-aligned custom primary actions for the wizard. Overwrites existing buttons (e.g. Cancel, Next, Finish).",
+ "isDefault": false,
+ "name": "customPrimaryActions",
+ "systemTags": [
+ "core",
+ ],
+ },
{
"description": "Specifies left-aligned secondary actions for the wizard. Use a button dropdown if multiple actions are required.",
"isDefault": false,
diff --git a/src/wizard/__tests__/wizard.test.tsx b/src/wizard/__tests__/wizard.test.tsx
index 5f7e43a54b..2e2c7ddbbc 100644
--- a/src/wizard/__tests__/wizard.test.tsx
+++ b/src/wizard/__tests__/wizard.test.tsx
@@ -515,6 +515,110 @@ describe('Custom actions', () => {
});
});
+describe('Custom primary actions', () => {
+ test('renders custom primary actions instead of default buttons', () => {
+ const customActions = (
+ <>
+
+
+ >
+ );
+ const [wrapper] = renderDefaultWizard({ customPrimaryActions: customActions });
+
+ expect(wrapper.findPrimaryButton()).toBeNull();
+ expect(wrapper.findCancelButton()).toBeNull();
+ expect(wrapper.findPreviousButton()).toBeNull();
+ expect(wrapper.findActions()!.findButton('[data-testid="custom-cancel"]')).not.toBeNull();
+ expect(wrapper.findActions()!.findButton('[data-testid="custom-next"]')).not.toBeNull();
+ });
+
+ test('custom primary actions work on first step', () => {
+ const onCustomClick = jest.fn();
+ const customActions = (
+
+ );
+
+ const [wrapper] = renderDefaultWizard({
+ customPrimaryActions: customActions,
+ activeStepIndex: 0,
+ });
+
+ const customActionButtonWrapper = wrapper.findActions()!.findButton('[data-testid="custom-action"]');
+ expect(customActionButtonWrapper).not.toBeNull();
+ customActionButtonWrapper!.click();
+ expect(onCustomClick).toHaveBeenCalledTimes(1);
+ });
+
+ test('custom primary actions work on middle step', () => {
+ const onCustomClick = jest.fn();
+ const customActions = (
+
+ );
+
+ const [wrapper] = renderDefaultWizard({
+ customPrimaryActions: customActions,
+ activeStepIndex: 1,
+ });
+
+ const customActionButtonWrapper = wrapper.findActions()!.findButton('[data-testid="custom-action"]');
+ expect(customActionButtonWrapper).not.toBeNull();
+ customActionButtonWrapper!.click();
+ expect(onCustomClick).toHaveBeenCalledTimes(1);
+ });
+
+ test('custom primary actions work on last step', () => {
+ const onCustomClick = jest.fn();
+ const customActions = (
+
+ );
+
+ const [wrapper] = renderDefaultWizard({
+ customPrimaryActions: customActions,
+ activeStepIndex: DEFAULT_STEPS.length - 1,
+ });
+
+ const customActionButtonWrapper = wrapper.findActions()!.findButton('[data-testid="custom-action"]');
+ expect(customActionButtonWrapper).not.toBeNull();
+ customActionButtonWrapper!.click();
+ expect(onCustomClick).toHaveBeenCalledTimes(1);
+ });
+
+ test('custom primary actions override skip-to button', () => {
+ const customActions = ;
+ const [wrapper] = renderDefaultWizard({
+ customPrimaryActions: customActions,
+ allowSkipTo: true,
+ });
+
+ expect(wrapper.findSkipToButton()).toBeNull();
+ expect(wrapper.findActions()!.findButton()!.getElement()).toHaveTextContent('Custom Only');
+ });
+
+ test('falls back to default actions when customPrimaryActions is null', () => {
+ const [wrapper] = renderDefaultWizard({ customPrimaryActions: null });
+
+ expect(wrapper.findPrimaryButton()).not.toBeNull();
+ expect(wrapper.findCancelButton()).not.toBeNull();
+ expect(wrapper.findPrimaryButton().getElement()).toHaveTextContent(DEFAULT_I18N_SETS[0].nextButton!);
+ });
+
+ test('falls back to default actions when customPrimaryActions is undefined', () => {
+ const [wrapper] = renderDefaultWizard({ customPrimaryActions: undefined });
+
+ expect(wrapper.findPrimaryButton()).not.toBeNull();
+ expect(wrapper.findCancelButton()).not.toBeNull();
+ expect(wrapper.findPrimaryButton().getElement()).toHaveTextContent(DEFAULT_I18N_SETS[0].nextButton!);
+ });
+});
+
describe('i18n', () => {
test('supports rendering static strings using i18n provider', () => {
const { container } = render(
diff --git a/src/wizard/interfaces.ts b/src/wizard/interfaces.ts
index 7a44e30478..97fb647e38 100644
--- a/src/wizard/interfaces.ts
+++ b/src/wizard/interfaces.ts
@@ -103,6 +103,13 @@ export interface WizardProps extends BaseComponentProps {
*/
allowSkipTo?: boolean;
+ /**
+ * Specifies right-aligned custom primary actions for the wizard. Overwrites existing buttons (e.g. Cancel, Next, Finish).
+ *
+ * @awsuiSystem core
+ */
+ customPrimaryActions?: React.ReactNode;
+
/**
* Specifies left-aligned secondary actions for the wizard. Use a button dropdown if multiple actions are required.
*/
diff --git a/src/wizard/internal.tsx b/src/wizard/internal.tsx
index fb82f3b807..1bbf37377b 100644
--- a/src/wizard/internal.tsx
+++ b/src/wizard/internal.tsx
@@ -41,6 +41,7 @@ export default function InternalWizard({
submitButtonText,
isLoadingNextStep = false,
allowSkipTo = false,
+ customPrimaryActions,
secondaryActions,
onCancel,
onSubmit,
@@ -201,6 +202,7 @@ export default function InternalWizard({
activeStepIndex={actualActiveStepIndex}
isPrimaryLoading={isLoadingNextStep}
allowSkipTo={allowSkipTo}
+ customPrimaryActions={customPrimaryActions}
secondaryActions={secondaryActions}
onCancelClick={onCancelClick}
onPreviousClick={onPreviousClick}
diff --git a/src/wizard/wizard-form.tsx b/src/wizard/wizard-form.tsx
index ef31e7b864..1db4b301e5 100644
--- a/src/wizard/wizard-form.tsx
+++ b/src/wizard/wizard-form.tsx
@@ -36,6 +36,7 @@ interface WizardFormProps extends InternalBaseComponentProps {
submitButtonText?: string;
isPrimaryLoading: boolean;
allowSkipTo: boolean;
+ customPrimaryActions?: React.ReactNode;
secondaryActions?: React.ReactNode;
onCancelClick: () => void;
onPreviousClick: () => void;
@@ -80,6 +81,7 @@ function WizardForm({
isPrimaryLoading,
allowSkipTo,
secondaryActions,
+ customPrimaryActions,
onCancelClick,
onPreviousClick,
onPrimaryClick,
@@ -151,25 +153,29 @@ function WizardForm({
__internalRootRef={ref}
className={styles['form-component']}
actions={
- onSkipToClick(skipToTargetIndex)}
- showPrevious={activeStepIndex !== 0}
- isPrimaryLoading={isPrimaryLoading}
- showSkipTo={showSkipTo}
- skipToButtonText={skipToButtonText}
- isLastStep={isLastStep}
- activeStepIndex={activeStepIndex}
- skipToStepIndex={skipToTargetIndex}
- />
+ customPrimaryActions ? (
+ customPrimaryActions
+ ) : (
+ onSkipToClick(skipToTargetIndex)}
+ showPrevious={activeStepIndex !== 0}
+ isPrimaryLoading={isPrimaryLoading}
+ showSkipTo={showSkipTo}
+ skipToButtonText={skipToButtonText}
+ isLastStep={isLastStep}
+ activeStepIndex={activeStepIndex}
+ skipToStepIndex={skipToTargetIndex}
+ />
+ )
}
secondaryActions={secondaryActions}
errorText={errorText}