diff --git a/.changeset/stale-trains-push.md b/.changeset/stale-trains-push.md
new file mode 100644
index 00000000000..e4dcc7db912
--- /dev/null
+++ b/.changeset/stale-trains-push.md
@@ -0,0 +1,5 @@
+---
+"@kaizen/components": minor
+---
+
+Migrate ProgressBar from kaizen-legacy
diff --git a/packages/components/src/ProgressBar/ProgressBar.module.scss b/packages/components/src/ProgressBar/ProgressBar.module.scss
new file mode 100644
index 00000000000..a8b09603222
--- /dev/null
+++ b/packages/components/src/ProgressBar/ProgressBar.module.scss
@@ -0,0 +1,88 @@
+@import "~@kaizen/design-tokens/sass/color";
+@import "~@kaizen/design-tokens/sass/spacing";
+
+$height: 10px;
+
+@mixin animation-background($color) {
+ background: linear-gradient(90deg, transparent, #{$color} 75%, transparent);
+}
+
+.subtext {
+ color: $color-purple-800;
+ padding-top: $spacing-6;
+ text-align: center;
+ opacity: 80%;
+}
+
+.progressBackground {
+ width: 100%;
+ background: $color-gray-300;
+ border-radius: $height;
+ height: $height;
+ overflow: hidden;
+ position: relative;
+}
+
+@keyframes pulse {
+ 0% {
+ transform: translateX(-100%);
+ }
+
+ 100% {
+ transform: translateX(200%);
+ }
+}
+
+.progress {
+ position: absolute;
+ inset: 0;
+ border-radius: $height;
+ overflow: hidden;
+ transition: transform 200ms ease;
+}
+
+.positive {
+ composes: progress;
+ background: $color-green-400;
+
+ &::after {
+ @include animation-background($color-green-300);
+ }
+}
+
+.informative {
+ composes: progress;
+ background: $color-blue-400;
+
+ &::after {
+ @include animation-background($color-blue-300);
+ }
+}
+
+.cautionary {
+ composes: progress;
+ background: $color-yellow-400;
+
+ &::after {
+ @include animation-background($color-yellow-300);
+ }
+}
+
+.negative {
+ composes: progress;
+ background: $color-red-400;
+}
+
+.isAnimating {
+ &::after {
+ opacity: 100%;
+ content: "";
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ width: 50%;
+ animation: pulse 2s infinite;
+ transition: opacity 0.2s;
+ }
+}
diff --git a/packages/components/src/ProgressBar/ProgressBar.spec.tsx b/packages/components/src/ProgressBar/ProgressBar.spec.tsx
new file mode 100644
index 00000000000..f2bb7dc4bc9
--- /dev/null
+++ b/packages/components/src/ProgressBar/ProgressBar.spec.tsx
@@ -0,0 +1,25 @@
+import React from "react"
+import { render, screen } from "@testing-library/react"
+import { ProgressBar } from "./ProgressBar"
+
+describe("", () => {
+ it("has an accessible progress value expressed as a percentage", () => {
+ const expectedAccessiblePercent: string = "60"
+
+ render(
+
+ )
+ expect(screen.getByTestId("id--progress-bar")).toHaveAttribute(
+ "aria-valuenow",
+ expectedAccessiblePercent
+ )
+ })
+})
diff --git a/packages/components/src/ProgressBar/ProgressBar.tsx b/packages/components/src/ProgressBar/ProgressBar.tsx
new file mode 100644
index 00000000000..c7e962af8d7
--- /dev/null
+++ b/packages/components/src/ProgressBar/ProgressBar.tsx
@@ -0,0 +1,74 @@
+import React, { HTMLAttributes } from "react"
+import classnames from "classnames"
+import { Heading } from "~components/Heading"
+import { OverrideClassName } from "~types/OverrideClassName"
+import { Label } from "./subcomponents/Label"
+import { calculatePercentage } from "./utils/calculatePercentage"
+import styles from "./ProgressBar.module.scss"
+
+export type ProgressBarProps = {
+ /** A value that represents completed progress */
+ value: number
+ /** A value that sets the maximum progress that can be achieved */
+ max: number
+ /** Adds an animated state to indicate loading progress */
+ isAnimating: boolean
+ mood: Mood
+ subtext?: string
+ label?: string
+ isReversed: boolean
+} & OverrideClassName>
+
+type Mood = "positive" | "informative" | "negative" | "cautionary"
+
+/**
+ * {@link https://cultureamp.atlassian.net/wiki/spaces/DesignSystem/pages/3081896891/Progress+Bar Guidance} |
+ * {@link https://cultureamp.design/?path=/docs/components-progress-bar--docs Storybook}
+ */
+export const ProgressBar = ({
+ value,
+ max,
+ isAnimating,
+ mood,
+ subtext,
+ label,
+ classNameOverride,
+ isReversed = false,
+ ...restProps
+}: ProgressBarProps): JSX.Element => {
+ const percentage = calculatePercentage({ value, max })
+ return (
+
+ {label &&
}
+
+ {subtext && (
+
+
+ {subtext}
+
+
+ )}
+
+ )
+}
+
+ProgressBar.displayName = "ProgressBar"
diff --git a/packages/components/src/ProgressBar/_docs/ProgressBar.mdx b/packages/components/src/ProgressBar/_docs/ProgressBar.mdx
new file mode 100644
index 00000000000..03d58880d67
--- /dev/null
+++ b/packages/components/src/ProgressBar/_docs/ProgressBar.mdx
@@ -0,0 +1,56 @@
+import { Canvas, Controls, DocsStory, Meta } from "@storybook/blocks"
+import { ResourceLinks, KaioNotification, Installation } from "~storybook/components"
+import * as ProgressBarStickersheetStories from "./ProgressBar.stickersheet.stories"
+import * as ProgressBarStories from "./ProgressBar.stories"
+
+
+
+# ProgressBar
+
+
+
+
+
+
+
+## Overview
+
+Progress bars show continuous progress through a process, such as a percentage value. They show how much progress is complete and how much remains.
+
+
+
+
+## API
+
+### isAnimating
+
+Adds an animated state that indicates system processes, such as downloading, uploading, or processing.
+
+
+
+### Moods
+
+
+
+### Value and max
+
+While `value` and `max` can be used to represent progress as either a percentage or fraction, screen readers will always announce this as a calculated percentage.
+
+
+
+
+
+### Accessible context
+
+Due to the optional `label` prop, the progress bar does not have an accessible name. You can provide context for assistive technologies with aria attributes such as `aria-label` or `aria-labelledby`.
+
+
+
diff --git a/packages/components/src/ProgressBar/_docs/ProgressBar.stickersheet.stories.tsx b/packages/components/src/ProgressBar/_docs/ProgressBar.stickersheet.stories.tsx
new file mode 100644
index 00000000000..7be20918e28
--- /dev/null
+++ b/packages/components/src/ProgressBar/_docs/ProgressBar.stickersheet.stories.tsx
@@ -0,0 +1,101 @@
+import React from "react"
+import { Meta } from "@storybook/react"
+import {
+ StickerSheet,
+ StickerSheetStory,
+} from "~storybook/components/StickerSheet"
+import { ProgressBar } from "../index"
+
+export default {
+ title: "Components/ProgressBar",
+ parameters: {
+ chromatic: { disable: false, pauseAnimationAtEnd: true },
+ controls: { disable: true },
+ a11y: {
+ config: {
+ rules: [
+ {
+ // `label` is an optional prop so this has no accessible by default. consumers can pass in `aria-labelledby` or `aria-label` which can provide an accessible description pending a refactor.
+ id: "aria-progressbar-name",
+ enabled: false,
+ },
+ ],
+ },
+ },
+ },
+} satisfies Meta
+
+const StickerSheetTemplate: StickerSheetStory = {
+ render: ({ isReversed = false }) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ),
+}
+
+export const StickerSheetDefault: StickerSheetStory = {
+ ...StickerSheetTemplate,
+ name: "Sticker Sheet (Default)",
+}
+
+export const StickerSheetReversed: StickerSheetStory = {
+ ...StickerSheetTemplate,
+ name: "Sticker Sheet (Reversed)",
+ parameters: {
+ backgrounds: { default: "Purple 700" },
+ },
+ args: { isReversed: true },
+}
+
+export const StickerSheetRTL: StickerSheetStory = {
+ ...StickerSheetTemplate,
+ name: "Sticker Sheet (RTL)",
+ parameters: {
+ textDirection: "rtl",
+ },
+}
diff --git a/packages/components/src/ProgressBar/_docs/ProgressBar.stories.tsx b/packages/components/src/ProgressBar/_docs/ProgressBar.stories.tsx
new file mode 100644
index 00000000000..7f13a85f5a8
--- /dev/null
+++ b/packages/components/src/ProgressBar/_docs/ProgressBar.stories.tsx
@@ -0,0 +1,90 @@
+import React from "react"
+import { Meta, StoryObj } from "@storybook/react"
+import { ProgressBar } from "../index"
+
+const meta = {
+ title: "Components/ProgressBar",
+ component: ProgressBar,
+ args: {
+ value: 25,
+ max: 100,
+ mood: "positive",
+ isAnimating: false,
+ label: "25%",
+ isReversed: false,
+ },
+} satisfies Meta
+
+const a11yExclusions = {
+ a11y: {
+ config: {
+ rules: [
+ {
+ // `label` is an optional prop so this has no accessible title by default. For now consumers can pass in `aria-labelledby` or `aria-label`, which can provide an accessible title.
+ id: "aria-progressbar-name",
+ enabled: false,
+ },
+ ],
+ },
+ },
+}
+
+export default meta
+
+type Story = StoryObj
+
+export const Playground: Story = {
+ parameters: {
+ docs: {
+ canvas: {
+ sourceState: "shown",
+ },
+ },
+ ...a11yExclusions,
+ },
+}
+
+export const IsAnimating: Story = {
+ args: { isAnimating: true },
+ parameters: {
+ ...a11yExclusions,
+ },
+}
+
+export const ValueAndMax: Story = {
+ render: () => (
+
+ ),
+ parameters: {
+ ...a11yExclusions,
+ },
+}
+
+export const Reversed: Story = {
+ parameters: {
+ backgrounds: { default: "Purple 700" },
+ ...a11yExclusions,
+ },
+ args: { isReversed: true },
+}
+
+export const AccessibleName: Story = {
+ args: { "aria-label": "Development goals" },
+}
diff --git a/packages/components/src/ProgressBar/index.ts b/packages/components/src/ProgressBar/index.ts
new file mode 100644
index 00000000000..8d7c35452f2
--- /dev/null
+++ b/packages/components/src/ProgressBar/index.ts
@@ -0,0 +1 @@
+export * from "./ProgressBar"
diff --git a/packages/components/src/ProgressBar/subcomponents/Label/Label.module.scss b/packages/components/src/ProgressBar/subcomponents/Label/Label.module.scss
new file mode 100644
index 00000000000..887476f0e54
--- /dev/null
+++ b/packages/components/src/ProgressBar/subcomponents/Label/Label.module.scss
@@ -0,0 +1,8 @@
+@import "~@kaizen/design-tokens/sass/color";
+@import "~@kaizen/design-tokens/sass/spacing";
+
+.label {
+ padding-bottom: $spacing-6;
+ color: $color-purple-800;
+ text-align: center;
+}
diff --git a/packages/components/src/ProgressBar/subcomponents/Label/Label.tsx b/packages/components/src/ProgressBar/subcomponents/Label/Label.tsx
new file mode 100644
index 00000000000..b138ede0c05
--- /dev/null
+++ b/packages/components/src/ProgressBar/subcomponents/Label/Label.tsx
@@ -0,0 +1,18 @@
+import React, { ReactNode } from "react"
+import { Heading } from "~components/Heading"
+import styles from "./Label.module.scss"
+
+type Label = {
+ content: ReactNode
+ isReversed: boolean
+}
+
+export const Label = ({ content, isReversed = false }: Label): JSX.Element => (
+
+
+ {content}
+
+
+)
+
+Label.displayName = "Label"
diff --git a/packages/components/src/ProgressBar/subcomponents/Label/index.ts b/packages/components/src/ProgressBar/subcomponents/Label/index.ts
new file mode 100644
index 00000000000..1c2ee0bd0c7
--- /dev/null
+++ b/packages/components/src/ProgressBar/subcomponents/Label/index.ts
@@ -0,0 +1 @@
+export * from "./Label"
diff --git a/packages/components/src/ProgressBar/utils/calculatePercentage.spec.ts b/packages/components/src/ProgressBar/utils/calculatePercentage.spec.ts
new file mode 100644
index 00000000000..95d2527495e
--- /dev/null
+++ b/packages/components/src/ProgressBar/utils/calculatePercentage.spec.ts
@@ -0,0 +1,9 @@
+import { calculatePercentage } from "./calculatePercentage"
+
+describe("calculatePercentage", () => {
+ it("returns a percentile of the value and max provided", async () => {
+ const expectedResult = 50
+
+ expect(calculatePercentage({ value: 2, max: 4 })).toEqual(expectedResult)
+ })
+})
diff --git a/packages/components/src/ProgressBar/utils/calculatePercentage.ts b/packages/components/src/ProgressBar/utils/calculatePercentage.ts
new file mode 100644
index 00000000000..be2f0d03b14
--- /dev/null
+++ b/packages/components/src/ProgressBar/utils/calculatePercentage.ts
@@ -0,0 +1,7 @@
+export const calculatePercentage = ({
+ value,
+ max,
+}: {
+ value: number
+ max: number
+}): number => (value / max) * 100.0
diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts
index 3b0bbb369e1..8e15233ecc1 100644
--- a/packages/components/src/index.ts
+++ b/packages/components/src/index.ts
@@ -57,3 +57,4 @@ export * from "./TimeField"
export * from "./Tooltip"
export * from "./Workflow"
export * from "./Well"
+export * from "./ProgressBar"