diff --git a/apps/web/content/docs/components/progress.mdx b/apps/web/content/docs/components/progress.mdx
index e475398a0..12a0c8c3e 100644
--- a/apps/web/content/docs/components/progress.mdx
+++ b/apps/web/content/docs/components/progress.mdx
@@ -43,6 +43,18 @@ Set your own custom colors for the progress bar component by using the `color` p
+## Circular Progress
+
+Use this Circular progress example to show a progress bar where you can set the progress rate using the `progress` prop from React which should be a number from 1 to 100.
+
+
+
+## Circular Progress With Text
+
+Use this Circular progress example to show a progress bar with a label. You can set the label text using the `textLabel` prop and the progress text using the `labelText` prop.
+
+
+
## Theme
To learn more about how to customize the appearance of components, please see the [Theme docs](/docs/customize/theme).
diff --git a/apps/web/examples/progress/index.ts b/apps/web/examples/progress/index.ts
index c43d89682..b8776e55c 100644
--- a/apps/web/examples/progress/index.ts
+++ b/apps/web/examples/progress/index.ts
@@ -1,3 +1,5 @@
+export { circularProgress } from "./progress.circular";
+export { circularProgressWithText } from "./progress.circularWithText";
export { colors } from "./progress.colors";
export { positioning } from "./progress.positioning";
export { root } from "./progress.root";
diff --git a/apps/web/examples/progress/progress.circular.tsx b/apps/web/examples/progress/progress.circular.tsx
new file mode 100644
index 000000000..54cf1f57a
--- /dev/null
+++ b/apps/web/examples/progress/progress.circular.tsx
@@ -0,0 +1,42 @@
+import { Progress } from "flowbite-react";
+import { type CodeData } from "~/components/code-demo";
+
+const code = `
+"use client";
+
+import { Progress } from "flowbite-react";
+
+export function Component() {
+ return ;
+}
+`;
+
+const codeRSC = `
+import { ProgressCircular } from "flowbite-react";
+
+export function Component() {
+ return ;
+}
+`;
+
+export function Component() {
+ return ;
+}
+
+export const circularProgress: CodeData = {
+ type: "single",
+ code: [
+ {
+ fileName: "client",
+ language: "tsx",
+ code,
+ },
+ {
+ fileName: "server",
+ language: "tsx",
+ code: codeRSC,
+ },
+ ],
+ githubSlug: "progress/progress.circular.tsx",
+ component: ,
+};
diff --git a/apps/web/examples/progress/progress.circularWithText.tsx b/apps/web/examples/progress/progress.circularWithText.tsx
new file mode 100644
index 000000000..91c19e1da
--- /dev/null
+++ b/apps/web/examples/progress/progress.circularWithText.tsx
@@ -0,0 +1,42 @@
+import { Progress } from "flowbite-react";
+import { type CodeData } from "~/components/code-demo";
+
+const code = `
+"use client";
+
+import { Progress } from "flowbite-react";
+
+export function Component() {
+ return ;
+}
+`;
+
+const codeRSC = `
+import { ProgressCircular } from "flowbite-react";
+
+export function Component() {
+ return ;
+}
+`;
+
+export function Component() {
+ return ;
+}
+
+export const circularProgressWithText: CodeData = {
+ type: "single",
+ code: [
+ {
+ fileName: "client",
+ language: "tsx",
+ code,
+ },
+ {
+ fileName: "server",
+ language: "tsx",
+ code: codeRSC,
+ },
+ ],
+ githubSlug: "progress/progress.circularWithText.tsx",
+ component: ,
+};
diff --git a/packages/ui/src/components/Progress/CircularProgress.stories.tsx b/packages/ui/src/components/Progress/CircularProgress.stories.tsx
new file mode 100644
index 000000000..17527da90
--- /dev/null
+++ b/packages/ui/src/components/Progress/CircularProgress.stories.tsx
@@ -0,0 +1,31 @@
+import type { Meta, StoryFn } from "@storybook/react";
+import type { CircularProgressProps } from "./ProgressCircular";
+import { CircularProgress } from "./ProgressCircular";
+
+export default {
+ title: "Components/Circular Progress",
+ component: CircularProgress,
+ decorators: [
+ (Story): JSX.Element => (
+
+
+
+ ),
+ ],
+} as Meta;
+
+const CircularTemplate: StoryFn = (args) => ;
+
+export const CircularProgressBar = CircularTemplate.bind({});
+CircularProgressBar.storyName = "Circular Progress";
+CircularProgressBar.args = {
+ progress: 25,
+};
+
+export const CircularProgressBarWithText = CircularTemplate.bind({});
+CircularProgressBarWithText.storyName = "Circular Progress With Text";
+CircularProgressBarWithText.args = {
+ progress: 25,
+ labelText: true,
+ textLabel: "25%",
+};
diff --git a/packages/ui/src/components/Progress/Progress.tsx b/packages/ui/src/components/Progress/Progress.tsx
index ead8c1a11..b61513465 100644
--- a/packages/ui/src/components/Progress/Progress.tsx
+++ b/packages/ui/src/components/Progress/Progress.tsx
@@ -5,6 +5,8 @@ import { mergeDeep } from "../../helpers/merge-deep";
import { getTheme } from "../../theme-store";
import type { DeepPartial, DynamicStringEnumKeysOf } from "../../types";
import type { FlowbiteColors, FlowbiteSizes } from "../Flowbite";
+import type { FlowbiteCircularProgressTheme } from "./ProgressCircular";
+import { CircularProgress } from "./ProgressCircular";
export interface FlowbiteProgressTheme {
base: string;
@@ -12,6 +14,7 @@ export interface FlowbiteProgressTheme {
bar: string;
color: ProgressColor;
size: ProgressSizes;
+ circular: FlowbiteCircularProgressTheme;
}
export interface ProgressColor
@@ -37,7 +40,7 @@ export interface ProgressProps extends ComponentProps<"div"> {
theme?: DeepPartial;
}
-export const Progress: FC = ({
+const ProgressComponent: FC = ({
className,
color = "cyan",
labelProgress = false,
@@ -83,4 +86,8 @@ export const Progress: FC = ({
);
};
-Progress.displayName = "Progress";
+ProgressComponent.displayName = "Progress";
+
+export const Progress = Object.assign(ProgressComponent, {
+ Circular: CircularProgress,
+});
diff --git a/packages/ui/src/components/Progress/ProgressCircular.tsx b/packages/ui/src/components/Progress/ProgressCircular.tsx
new file mode 100644
index 000000000..c83769fbf
--- /dev/null
+++ b/packages/ui/src/components/Progress/ProgressCircular.tsx
@@ -0,0 +1,92 @@
+import type { ComponentProps, FC } from "react";
+import { useId, useMemo } from "react";
+import { twMerge } from "tailwind-merge";
+import { mergeDeep } from "../../helpers/merge-deep";
+import { getTheme } from "../../theme-store";
+import type { DeepPartial } from "../../types";
+import type { FlowbiteColors } from "../Flowbite";
+
+export interface FlowbiteCircularProgressTheme {
+ base: string;
+ bar: string;
+ label: {
+ base: string;
+ text: string;
+ textColor: CircularProgressColor;
+ };
+ color: {
+ bgColor: string;
+ barColor: CircularProgressColor;
+ };
+}
+
+export interface CircularProgressColor
+ extends Pick<
+ FlowbiteColors,
+ "dark" | "blue" | "red" | "green" | "yellow" | "indigo" | "purple" | "cyan" | "gray" | "lime" | "pink" | "teal"
+ > {
+ [key: string]: string;
+}
+
+export interface CircularProgressProps extends ComponentProps<"div"> {
+ labelText?: boolean;
+ progress: number;
+ textLabel?: string;
+ theme?: DeepPartial;
+ progressColor?: keyof CircularProgressColor;
+}
+
+export const CircularProgress: FC = ({
+ className,
+ progressColor = "cyan",
+ labelText = false,
+ progress,
+ textLabel = "65%",
+ theme: customTheme = {},
+ ...props
+}) => {
+ const id = useId();
+ const theme = mergeDeep(getTheme().progress.circular, customTheme);
+
+ // Memoize calculations for the circumference and stroke offset to avoid recalculating on each render
+ const { offset } = useMemo(() => {
+ const circumference = 2 * Math.PI * 16; // Fixed radius of 16
+
+ const offset = circumference * (1 - progress / 100); // Stroke dash offset based on progress
+
+ return { offset };
+ }, [progress]);
+
+ return (
+
+
+
+
+ {labelText && textLabel ? (
+
+
+ {textLabel}
+
+
+ ) : null}
+
+
+ );
+};
diff --git a/packages/ui/src/components/Progress/theme.ts b/packages/ui/src/components/Progress/theme.ts
index 2640133d1..451de776d 100644
--- a/packages/ui/src/components/Progress/theme.ts
+++ b/packages/ui/src/components/Progress/theme.ts
@@ -25,4 +25,43 @@ export const progressTheme: FlowbiteProgressTheme = createTheme({
lg: "h-4",
xl: "h-6",
},
+ circular: {
+ base: "relative size-40",
+ bar: "size-full -rotate-90",
+ color: {
+ barColor: {
+ dark: "stroke-current text-gray-600 dark:text-gray-300",
+ blue: "stroke-current text-blue-600",
+ red: "stroke-current text-red-600 dark:text-red-500",
+ green: "stroke-current text-green-600 dark:text-green-500",
+ yellow: "stroke-current text-yellow-400",
+ indigo: "stroke-current text-indigo-600 dark:text-indigo-500",
+ purple: "stroke-current text-purple-600 dark:text-purple-500",
+ cyan: "stroke-current text-cyan-600",
+ gray: "stroke-current text-gray-500",
+ lime: "stroke-current text-lime-600",
+ pink: "stroke-current text-pink-500",
+ teal: "stroke-current text-teal-600",
+ },
+ bgColor: "stroke-current text-gray-200 dark:text-neutral-700",
+ },
+ label: {
+ base: "absolute start-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 transform",
+ text: "text-center text-2xl font-bold",
+ textColor: {
+ dark: "text-gray-600 dark:text-gray-300",
+ blue: "text-blue-600",
+ red: "text-red-600 dark:text-red-500",
+ green: "text-green-600 dark:text-green-500",
+ yellow: "text-yellow-400",
+ indigo: "text-indigo-600 dark:text-indigo-500",
+ purple: "text-purple-600 dark:text-purple-500",
+ cyan: "text-cyan-600",
+ gray: "text-gray-500",
+ lime: "text-lime-600",
+ pink: "text-pink-500",
+ teal: "text-teal-600",
+ },
+ },
+ },
});