diff --git a/apps/web/content/docs/components/skeleton.mdx b/apps/web/content/docs/components/skeleton.mdx
new file mode 100644
index 000000000..a0de92dc9
--- /dev/null
+++ b/apps/web/content/docs/components/skeleton.mdx
@@ -0,0 +1,76 @@
+---
+title: React Skeleton - Flowbite
+description: The skeleton component can be used as an alternative loading indicator to the spinner by mimicking the content that will be loaded such as text, images, or video
+---
+
+Use the skeleton component to indicate a loading status with placeholder elements that look very similar to the type of content that is being loaded such as paragraphs, headings, images, videos, and more.
+
+You can set the width and height of these skeleton components based on the size of the content and element that it is being loaded in, such as a card or an article page.
+
+Start using the skeleton component by importing it from the `flowbite-react` library:
+
+```jsx
+import { Skeleton } from "flowbite-react";
+```
+
+# Variants
+
+## Default
+
+Represents a single line of text.
+
+
+
+## Circular
+
+
+
+## Rectangular
+
+
+
+## Rounded
+
+
+
+# Examples
+
+## Image placeholder
+
+This example can be used to show a placeholder when loading an image and text content.
+
+
+
+## Video placeholder
+
+Use this example to show a skeleton placeholder when loading video content.
+
+
+
+## Card placeholder
+
+Use this example to show a placeholder when loading content inside a card.
+
+
+
+## List placeholder
+
+Use this example to show a placeholder when loading a list of items.
+
+
+
+## Testimonial placeholder
+
+Use this example to show a placeholder when loading a list of items.
+
+
+
+# Theme
+
+To learn more about how to customize the appearance of components, please see the [Theme docs](/docs/customize/theme).
+
+
+
+# References
+
+- [Flowbite Skeleton](https://flowbite.com/docs/components/skeleton/)
diff --git a/apps/web/data/components.tsx b/apps/web/data/components.tsx
index 29e7e8619..d73368c65 100644
--- a/apps/web/data/components.tsx
+++ b/apps/web/data/components.tsx
@@ -140,6 +140,13 @@ export const COMPONENTS_DATA: Component[] = [
link: "/docs/components/sidebar",
classes: "w-16",
},
+ {
+ name: "Skeleton",
+ image: "/images/components/skeleton.svg",
+ imageDark: "/images/components/skeleton-dark.svg",
+ link: "/docs/components/skeleton",
+ classes: "w-48",
+ },
{
name: "Pagination",
image: "/images/components/pagination.svg",
diff --git a/apps/web/data/docs-sidebar.ts b/apps/web/data/docs-sidebar.ts
index 60c49560b..6f8670e76 100644
--- a/apps/web/data/docs-sidebar.ts
+++ b/apps/web/data/docs-sidebar.ts
@@ -76,6 +76,7 @@ export const DOCS_SIDEBAR: DocsSidebarSection[] = [
{ title: "Progress bar", href: "/docs/components/progress" },
{ title: "Rating", href: "/docs/components/rating" },
{ title: "Sidebar", href: "/docs/components/sidebar" },
+ { title: "Skeleton", href: "/docs/components/skeleton", isNew: true },
{ title: "Spinner", href: "/docs/components/spinner" },
{ title: "Table", href: "/docs/components/table" },
{ title: "Tabs", href: "/docs/components/tabs" },
diff --git a/apps/web/examples/index.ts b/apps/web/examples/index.ts
index 7992d8e29..f7cb3220f 100644
--- a/apps/web/examples/index.ts
+++ b/apps/web/examples/index.ts
@@ -25,6 +25,7 @@ export * as popover from "./popover";
export * as progress from "./progress";
export * as rating from "./rating";
export * as sidebar from "./sidebar";
+export * as skeleton from "./skeleton";
export * as spinner from "./spinner";
export * as table from "./table";
export * as tabs from "./tabs";
diff --git a/apps/web/examples/skeleton/index.ts b/apps/web/examples/skeleton/index.ts
new file mode 100644
index 000000000..2a9ad42c2
--- /dev/null
+++ b/apps/web/examples/skeleton/index.ts
@@ -0,0 +1,9 @@
+export { card } from "./skeleton.card";
+export { circular } from "./skeleton.circular";
+export { image } from "./skeleton.image";
+export { list } from "./skeleton.list";
+export { rectangular } from "./skeleton.rectangular";
+export { rounded } from "./skeleton.rounded";
+export { root } from "./skeleton.root";
+export { testimonial } from "./skeleton.testimonial";
+export { video } from "./skeleton.video";
diff --git a/apps/web/examples/skeleton/skeleton.card.tsx b/apps/web/examples/skeleton/skeleton.card.tsx
new file mode 100644
index 000000000..fdc201a7a
--- /dev/null
+++ b/apps/web/examples/skeleton/skeleton.card.tsx
@@ -0,0 +1,54 @@
+import { SkeletonCard } from "flowbite-react";
+import type { CodeData } from "~/components/code-demo";
+
+const code = `
+"use client";
+
+import { Skeleton } from "flowbite-react";
+
+function Component() {
+ return (
+
+
+
+ )
+}
+`;
+
+const codeRSC = `
+import { SkeletonCard } from "flowbite-react";
+
+function Component() {
+ return (
+
+
+
+ )
+}
+`;
+
+function Component() {
+ return (
+
+
+
+ );
+}
+
+export const card: CodeData = {
+ type: "single",
+ code: [
+ {
+ fileName: "client",
+ language: "tsx",
+ code,
+ },
+ {
+ fileName: "server",
+ language: "tsx",
+ code: codeRSC,
+ },
+ ],
+ githubSlug: "skeleton/skeleton.card.tsx",
+ component: ,
+};
diff --git a/apps/web/examples/skeleton/skeleton.circular.tsx b/apps/web/examples/skeleton/skeleton.circular.tsx
new file mode 100644
index 000000000..831738930
--- /dev/null
+++ b/apps/web/examples/skeleton/skeleton.circular.tsx
@@ -0,0 +1,54 @@
+import { Skeleton } from "flowbite-react";
+import type { CodeData } from "~/components/code-demo";
+
+const code = `
+"use client";
+
+import { Skeleton } from "flowbite-react";
+
+function Component() {
+ return (
+
+
+
+ )
+}
+`;
+
+const codeRSC = `
+import { Skeleton } from "flowbite-react";
+
+function Component() {
+ return (
+
+
+
+ )
+}
+`;
+
+function Component() {
+ return (
+
+
+
+ );
+}
+
+export const circular: CodeData = {
+ type: "single",
+ code: [
+ {
+ fileName: "client",
+ language: "tsx",
+ code,
+ },
+ {
+ fileName: "server",
+ language: "tsx",
+ code: codeRSC,
+ },
+ ],
+ githubSlug: "skeleton/skeleton.circular.tsx",
+ component: ,
+};
diff --git a/apps/web/examples/skeleton/skeleton.image.tsx b/apps/web/examples/skeleton/skeleton.image.tsx
new file mode 100644
index 000000000..6feeb5e85
--- /dev/null
+++ b/apps/web/examples/skeleton/skeleton.image.tsx
@@ -0,0 +1,54 @@
+import { SkeletonImage } from "flowbite-react";
+import type { CodeData } from "~/components/code-demo";
+
+const code = `
+"use client";
+
+import { Skeleton } from "flowbite-react";
+
+function Component() {
+ return (
+
+
+
+ )
+}
+`;
+
+const codeRSC = `
+import { SkeletonImage } from "flowbite-react";
+
+function Component() {
+ return (
+
+
+
+ )
+}
+`;
+
+function Component() {
+ return (
+
+
+
+ );
+}
+
+export const image: CodeData = {
+ type: "single",
+ code: [
+ {
+ fileName: "client",
+ language: "tsx",
+ code,
+ },
+ {
+ fileName: "server",
+ language: "tsx",
+ code: codeRSC,
+ },
+ ],
+ githubSlug: "skeleton/skeleton.image.tsx",
+ component: ,
+};
diff --git a/apps/web/examples/skeleton/skeleton.list.tsx b/apps/web/examples/skeleton/skeleton.list.tsx
new file mode 100644
index 000000000..4bc4f12cf
--- /dev/null
+++ b/apps/web/examples/skeleton/skeleton.list.tsx
@@ -0,0 +1,54 @@
+import { SkeletonList } from "flowbite-react";
+import type { CodeData } from "~/components/code-demo";
+
+const code = `
+"use client";
+
+import { Skeleton } from "flowbite-react";
+
+function Component() {
+ return (
+
+
+
+ )
+}
+`;
+
+const codeRSC = `
+import { SkeletonList } from "flowbite-react";
+
+function Component() {
+ return (
+
+
+
+ )
+}
+`;
+
+function Component() {
+ return (
+
+
+
+ );
+}
+
+export const list: CodeData = {
+ type: "single",
+ code: [
+ {
+ fileName: "client",
+ language: "tsx",
+ code,
+ },
+ {
+ fileName: "server",
+ language: "tsx",
+ code: codeRSC,
+ },
+ ],
+ githubSlug: "skeleton/skeleton.list.tsx",
+ component: ,
+};
diff --git a/apps/web/examples/skeleton/skeleton.rectangular.tsx b/apps/web/examples/skeleton/skeleton.rectangular.tsx
new file mode 100644
index 000000000..bde99a411
--- /dev/null
+++ b/apps/web/examples/skeleton/skeleton.rectangular.tsx
@@ -0,0 +1,54 @@
+import { Skeleton } from "flowbite-react";
+import type { CodeData } from "~/components/code-demo";
+
+const code = `
+'use client';
+
+import { Skeleton } from "flowbite-react";
+
+function Component() {
+ return (
+
+
+
+ )
+}
+`;
+
+const codeRSC = `
+import { Skeleton } from "flowbite-react";
+
+function Component() {
+ return (
+
+
+
+ )
+}
+`;
+
+function Component() {
+ return (
+
+
+
+ );
+}
+
+export const rectangular: CodeData = {
+ type: "single",
+ code: [
+ {
+ fileName: "client",
+ language: "tsx",
+ code,
+ },
+ {
+ fileName: "server",
+ language: "tsx",
+ code: codeRSC,
+ },
+ ],
+ githubSlug: "skeleton/skeleton.rectangular.tsx",
+ component: ,
+};
diff --git a/apps/web/examples/skeleton/skeleton.root.tsx b/apps/web/examples/skeleton/skeleton.root.tsx
new file mode 100644
index 000000000..ebf6b3729
--- /dev/null
+++ b/apps/web/examples/skeleton/skeleton.root.tsx
@@ -0,0 +1,54 @@
+import { Skeleton } from "flowbite-react";
+import type { CodeData } from "~/components/code-demo";
+
+const code = `
+'use client';
+
+import { Skeleton } from "flowbite-react";
+
+function Component() {
+ return (
+
+
+
+ )
+}
+`;
+
+const codeRSC = `
+import { Skeleton } from "flowbite-react";
+
+function Component() {
+ return (
+
+
+
+ )
+}
+`;
+
+function Component() {
+ return (
+
+
+
+ );
+}
+
+export const root: CodeData = {
+ type: "single",
+ code: [
+ {
+ fileName: "client",
+ language: "tsx",
+ code,
+ },
+ {
+ fileName: "server",
+ language: "tsx",
+ code: codeRSC,
+ },
+ ],
+ githubSlug: "skeleton/skeleton.root.tsx",
+ component: ,
+};
diff --git a/apps/web/examples/skeleton/skeleton.rounded.tsx b/apps/web/examples/skeleton/skeleton.rounded.tsx
new file mode 100644
index 000000000..48e541eb7
--- /dev/null
+++ b/apps/web/examples/skeleton/skeleton.rounded.tsx
@@ -0,0 +1,54 @@
+import { Skeleton } from "flowbite-react";
+import type { CodeData } from "~/components/code-demo";
+
+const code = `
+'use client';
+
+import { Skeleton } from "flowbite-react";
+
+function Component() {
+ return (
+
+
+
+ )
+}
+`;
+
+const codeRSC = `
+import { Skeleton } from "flowbite-react";
+
+function Component() {
+ return (
+
+
+
+ )
+}
+`;
+
+function Component() {
+ return (
+
+
+
+ );
+}
+
+export const rounded: CodeData = {
+ type: "single",
+ code: [
+ {
+ fileName: "client",
+ language: "tsx",
+ code,
+ },
+ {
+ fileName: "server",
+ language: "tsx",
+ code: codeRSC,
+ },
+ ],
+ githubSlug: "skeleton/skeleton.rounded.tsx",
+ component: ,
+};
diff --git a/apps/web/examples/skeleton/skeleton.testimonial.tsx b/apps/web/examples/skeleton/skeleton.testimonial.tsx
new file mode 100644
index 000000000..d138018bc
--- /dev/null
+++ b/apps/web/examples/skeleton/skeleton.testimonial.tsx
@@ -0,0 +1,54 @@
+import { SkeletonTestimonial } from "flowbite-react";
+import type { CodeData } from "~/components/code-demo";
+
+const code = `
+'use client';
+
+import { Skeleton } from "flowbite-react";
+
+function Component() {
+ return (
+
+
+
+ )
+}
+`;
+
+const codeRSC = `
+import { SkeletonTestimonial } from "flowbite-react";
+
+function Component() {
+ return (
+
+
+
+ )
+}
+`;
+
+function Component() {
+ return (
+
+
+
+ );
+}
+
+export const testimonial: CodeData = {
+ type: "single",
+ code: [
+ {
+ fileName: "client",
+ language: "tsx",
+ code,
+ },
+ {
+ fileName: "server",
+ language: "tsx",
+ code: codeRSC,
+ },
+ ],
+ githubSlug: "skeleton/skeleton.testimonial.tsx",
+ component: ,
+};
diff --git a/apps/web/examples/skeleton/skeleton.video.tsx b/apps/web/examples/skeleton/skeleton.video.tsx
new file mode 100644
index 000000000..e73aa1f0e
--- /dev/null
+++ b/apps/web/examples/skeleton/skeleton.video.tsx
@@ -0,0 +1,54 @@
+import { SkeletonVideo } from "flowbite-react";
+import type { CodeData } from "~/components/code-demo";
+
+const code = `
+'use client';
+
+import { Skeleton } from "flowbite-react";
+
+function Component() {
+ return (
+
+
+
+ )
+}
+`;
+
+const codeRSC = `
+import { SkeletonVideo } from "flowbite-react";
+
+function Component() {
+ return (
+
+
+
+ )
+}
+`;
+
+function Component() {
+ return (
+
+
+
+ );
+}
+
+export const video: CodeData = {
+ type: "single",
+ code: [
+ {
+ fileName: "client",
+ language: "tsx",
+ code,
+ },
+ {
+ fileName: "server",
+ language: "tsx",
+ code: codeRSC,
+ },
+ ],
+ githubSlug: "skeleton/skeleton.video.tsx",
+ component: ,
+};
diff --git a/apps/web/public/images/components/skeleton-dark.svg b/apps/web/public/images/components/skeleton-dark.svg
new file mode 100644
index 000000000..c8a9d4955
--- /dev/null
+++ b/apps/web/public/images/components/skeleton-dark.svg
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/web/public/images/components/skeleton.svg b/apps/web/public/images/components/skeleton.svg
new file mode 100644
index 000000000..11977a7a9
--- /dev/null
+++ b/apps/web/public/images/components/skeleton.svg
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/ui/src/components/Flowbite/FlowbiteTheme.ts b/packages/ui/src/components/Flowbite/FlowbiteTheme.ts
index a0ce75db5..ce6c8a396 100644
--- a/packages/ui/src/components/Flowbite/FlowbiteTheme.ts
+++ b/packages/ui/src/components/Flowbite/FlowbiteTheme.ts
@@ -30,6 +30,7 @@ import type { FlowbiteRangeSliderTheme } from "../RangeSlider";
import type { FlowbiteRatingAdvancedTheme, FlowbiteRatingTheme } from "../Rating";
import type { FlowbiteSelectTheme } from "../Select";
import type { FlowbiteSidebarTheme } from "../Sidebar";
+import type { FlowbiteSkeletonTheme } from "../Skeleton";
import type { FlowbiteSpinnerTheme } from "../Spinner";
import type { FlowbiteTableTheme } from "../Table";
import type { FlowbiteTabsTheme } from "../Tabs";
@@ -76,6 +77,7 @@ export interface FlowbiteTheme {
ratingAdvanced: FlowbiteRatingAdvancedTheme;
select: FlowbiteSelectTheme;
sidebar: FlowbiteSidebarTheme;
+ skeleton: FlowbiteSkeletonTheme;
spinner: FlowbiteSpinnerTheme;
table: FlowbiteTableTheme;
tabs: FlowbiteTabsTheme;
diff --git a/packages/ui/src/components/Skeleton/Skeleton.spec.tsx b/packages/ui/src/components/Skeleton/Skeleton.spec.tsx
new file mode 100644
index 000000000..ce4183500
--- /dev/null
+++ b/packages/ui/src/components/Skeleton/Skeleton.spec.tsx
@@ -0,0 +1,57 @@
+import { render, screen } from "@testing-library/react";
+import { describe, expect, it } from "vitest";
+import { Skeleton } from "./Skeleton";
+
+describe("Components / Skeleton", () => {
+ it("should have default Skeleton in Document", async () => {
+ render( );
+
+ expect(defaultSkeleton()).toBeInTheDocument();
+ });
+
+ it("should have Skeleton.Card in the document", async () => {
+ render( );
+
+ expect(skeletonCard()).toBeInTheDocument();
+ });
+
+ it("should have Skeleton.Image in the document", async () => {
+ render( );
+
+ expect(skeletonImg()).toBeInTheDocument();
+ });
+
+ it("should have Skeleton.List in the document", async () => {
+ render( );
+
+ expect(skeletonList()).toBeInTheDocument();
+ });
+
+ it("should have Skeleton.Testimonial in the document", async () => {
+ render( );
+
+ expect(skeletonTestimonial()).toBeInTheDocument();
+ });
+
+ it("should have Skeleton.Video in the document", async () => {
+ render( );
+
+ expect(skeletonVideo()).toBeInTheDocument();
+ });
+
+ it('should have role="Status" in the document', async () => {
+ render( );
+
+ getSkeletonByStatus().forEach((status) => {
+ expect(status).toBeInTheDocument();
+ });
+ });
+});
+
+const defaultSkeleton = () => screen.getByTestId("flowbite-skeleton");
+const skeletonCard = () => screen.getByTestId("flowbite-skeleton-card");
+const skeletonImg = () => screen.getByTestId("flowbite-skeleton-image");
+const skeletonList = () => screen.getByTestId("flowbite-skeleton-list");
+const skeletonTestimonial = () => screen.getByTestId("flowbite-skeleton-testimonial");
+const skeletonVideo = () => screen.getByTestId("flowbite-skeleton-video");
+const getSkeletonByStatus = () => screen.getAllByRole("status");
diff --git a/packages/ui/src/components/Skeleton/Skeleton.stories.tsx b/packages/ui/src/components/Skeleton/Skeleton.stories.tsx
new file mode 100644
index 000000000..de909f0e4
--- /dev/null
+++ b/packages/ui/src/components/Skeleton/Skeleton.stories.tsx
@@ -0,0 +1,70 @@
+import type { Meta, StoryFn } from "@storybook/react";
+import { Skeleton } from "./Skeleton";
+
+export default {
+ title: "Components/Skeleton",
+ component: Skeleton,
+} as Meta;
+
+const Template: StoryFn = (args) => {
+ return (
+
+
+
+ );
+};
+
+export const Default = Template.bind({});
+Default.args = {
+ variant: "default",
+};
+
+const CardTemplate: StoryFn = (args) => {
+ return (
+
+
+
+ );
+};
+
+export const CardSkeleton = CardTemplate.bind({});
+
+const ImageTemplate: StoryFn = (args) => {
+ return (
+
+
+
+ );
+};
+
+export const ImageSkeleton = ImageTemplate.bind({});
+
+const ListTemplate: StoryFn = (args) => {
+ return (
+
+
+
+ );
+};
+
+export const ListSkeleton = ListTemplate.bind({});
+
+const TestimonialTemplate: StoryFn = (args) => {
+ return (
+
+
+
+ );
+};
+
+export const TestimonialSkeleton = TestimonialTemplate.bind({});
+
+const VideoTemplate: StoryFn = (args) => {
+ return (
+
+
+
+ );
+};
+
+export const VideoSkeleton = VideoTemplate.bind({});
diff --git a/packages/ui/src/components/Skeleton/Skeleton.tsx b/packages/ui/src/components/Skeleton/Skeleton.tsx
new file mode 100644
index 000000000..db5057ccf
--- /dev/null
+++ b/packages/ui/src/components/Skeleton/Skeleton.tsx
@@ -0,0 +1,68 @@
+import type { ComponentProps, FC } from "react";
+import { twMerge } from "tailwind-merge";
+import { mergeDeep } from "../../helpers/merge-deep";
+import { getTheme } from "../../theme-store";
+import type { DeepPartial } from "../../types";
+import { SkeletonCard, type FlowbiteSkeletonCardTheme } from "./SkeletonCard";
+import { SkeletonImage, type FlowbiteSkeletonImageTheme } from "./SkeletonImage";
+import { SkeletonList, type FlowbiteSkeletonListTheme } from "./SkeletonList";
+import { SkeletonTestimonial, type FlowbiteSkeletonTestimonialTheme } from "./SkeletonTestimonial";
+import { SkeletonVideo, type FlowbiteSkeletonVideoTheme } from "./SkeletonVideo";
+
+export interface FlowbiteSkeletonTheme {
+ root: FlowbiteSkeletonRootTheme;
+ variant: {
+ base: string;
+ type: {
+ default: string;
+ rectangular: string;
+ rounded: string;
+ circular: string;
+ };
+ };
+ image: FlowbiteSkeletonImageTheme;
+ video: FlowbiteSkeletonVideoTheme;
+ card: FlowbiteSkeletonCardTheme;
+ list: FlowbiteSkeletonListTheme;
+ testimonial: FlowbiteSkeletonTestimonialTheme;
+}
+
+export interface FlowbiteSkeletonRootTheme {
+ base: string;
+}
+
+export interface SkeletonProps extends ComponentProps<"div"> {
+ theme?: DeepPartial;
+ variant?: "default" | "rectangular" | "rounded" | "circular";
+}
+
+const SkeletonComponent: FC = ({
+ className,
+ theme: customTheme = {},
+ variant: skeletonVariant = "default",
+ ...props
+}) => {
+ const theme = mergeDeep(getTheme().skeleton, customTheme);
+
+ return (
+
+ );
+};
+
+SkeletonComponent.displayName = "Skeleton";
+SkeletonImage.displayName = "Skeleton.Image";
+SkeletonVideo.displayName = "Skeleton.Video";
+SkeletonCard.displayName = "Skeleton.Card";
+SkeletonList.displayName = "Skeleton.List";
+SkeletonTestimonial.displayName = "Skeleton.Testimonial";
+
+export const Skeleton = Object.assign(SkeletonComponent, {
+ Image: SkeletonImage,
+ Video: SkeletonVideo,
+ Card: SkeletonCard,
+ List: SkeletonList,
+ Testimonial: SkeletonTestimonial,
+});
diff --git a/packages/ui/src/components/Skeleton/SkeletonCard.tsx b/packages/ui/src/components/Skeleton/SkeletonCard.tsx
new file mode 100644
index 000000000..71a8c5593
--- /dev/null
+++ b/packages/ui/src/components/Skeleton/SkeletonCard.tsx
@@ -0,0 +1,64 @@
+import type { FC } from "react";
+import { twMerge } from "tailwind-merge";
+import { mergeDeep } from "../../helpers/merge-deep";
+import { getTheme } from "../../theme-store";
+import type { DeepPartial } from "../../types";
+
+export interface FlowbiteSkeletonCardTheme {
+ base: string;
+ cardImg: {
+ base: string;
+ svg: string;
+ };
+ text: string;
+ userIcon: {
+ base: string;
+ icon: string;
+ text: string;
+ };
+}
+
+export interface SkeletonCardProps {
+ theme?: DeepPartial;
+ className?: string;
+}
+
+export const SkeletonCard: FC = ({ className, theme: customTheme = {} }) => {
+ const theme = mergeDeep(getTheme().skeleton.card, customTheme);
+
+ return (
+
+ );
+};
diff --git a/packages/ui/src/components/Skeleton/SkeletonImage.tsx b/packages/ui/src/components/Skeleton/SkeletonImage.tsx
new file mode 100644
index 000000000..cc10483a0
--- /dev/null
+++ b/packages/ui/src/components/Skeleton/SkeletonImage.tsx
@@ -0,0 +1,55 @@
+import type { FC } from "react";
+import { twMerge } from "tailwind-merge";
+import { mergeDeep } from "../../helpers/merge-deep";
+import { getTheme } from "../../theme-store";
+import type { DeepPartial } from "../../types";
+
+export interface FlowbiteSkeletonImageTheme {
+ base: string;
+ imgDiv: string;
+ imgSvg: string;
+ texts: {
+ base: string;
+ lineOne: string;
+ lineTwo: string;
+ lineThree: string;
+ lineFour: string;
+ lineFive: string;
+ lineSix: string;
+ };
+}
+
+export interface SkeletonImageProps {
+ theme?: DeepPartial;
+ className?: string;
+}
+
+export const SkeletonImage: FC = ({ className, theme: customTheme = {} }) => {
+ const theme = mergeDeep(getTheme().skeleton.image, customTheme);
+
+ return (
+
+ );
+};
diff --git a/packages/ui/src/components/Skeleton/SkeletonList.tsx b/packages/ui/src/components/Skeleton/SkeletonList.tsx
new file mode 100644
index 000000000..0b3aa4663
--- /dev/null
+++ b/packages/ui/src/components/Skeleton/SkeletonList.tsx
@@ -0,0 +1,53 @@
+import type { FC } from "react";
+import { twMerge } from "tailwind-merge";
+import { mergeDeep } from "../../helpers/merge-deep";
+import { getTheme } from "../../theme-store";
+import type { DeepPartial } from "../../types";
+
+export interface FlowbiteSkeletonListTheme {
+ base: string;
+ textList: {
+ base: string;
+ list: {
+ textOne: string;
+ textTwo: string;
+ textThree: string;
+ };
+ };
+}
+
+export interface SkeletonListProps {
+ theme?: DeepPartial;
+ className?: string;
+}
+
+export const SkeletonList: FC = ({ className, theme: customTheme = {} }) => {
+ const theme = mergeDeep(getTheme().skeleton.list, customTheme);
+
+ return (
+
+ );
+};
diff --git a/packages/ui/src/components/Skeleton/SkeletonTestimonial.tsx b/packages/ui/src/components/Skeleton/SkeletonTestimonial.tsx
new file mode 100644
index 000000000..8a445a19c
--- /dev/null
+++ b/packages/ui/src/components/Skeleton/SkeletonTestimonial.tsx
@@ -0,0 +1,49 @@
+import type { FC } from "react";
+import { twMerge } from "tailwind-merge";
+import { mergeDeep } from "../../helpers/merge-deep";
+import { getTheme } from "../../theme-store";
+import type { DeepPartial } from "../../types";
+
+export interface FlowbiteSkeletonTestimonialTheme {
+ base: string;
+ textList: {
+ firstLine: string;
+ secondLine: string;
+ author: {
+ base: string;
+ userIcon: string;
+ authorName: string;
+ secondText: string;
+ };
+ };
+}
+
+export interface SkeletonTestimonialProps {
+ theme?: DeepPartial;
+ className?: string;
+}
+
+export const SkeletonTestimonial: FC = ({ className, theme: customTheme = {} }) => {
+ const theme = mergeDeep(getTheme().skeleton.testimonial, customTheme);
+
+ return (
+
+ );
+};
diff --git a/packages/ui/src/components/Skeleton/SkeletonVideo.tsx b/packages/ui/src/components/Skeleton/SkeletonVideo.tsx
new file mode 100644
index 000000000..0da0ee15e
--- /dev/null
+++ b/packages/ui/src/components/Skeleton/SkeletonVideo.tsx
@@ -0,0 +1,35 @@
+import type { FC } from "react";
+import { twMerge } from "tailwind-merge";
+import { mergeDeep } from "../../helpers/merge-deep";
+import { getTheme } from "../../theme-store";
+import type { DeepPartial } from "../../types";
+
+export interface FlowbiteSkeletonVideoTheme {
+ base: string;
+ svg: string;
+}
+
+export interface SkeletonVideoProps {
+ theme?: DeepPartial;
+ className?: string;
+}
+
+export const SkeletonVideo: FC = ({ className, theme: customTheme = {} }) => {
+ const theme = mergeDeep(getTheme().skeleton.video, customTheme);
+
+ return (
+
+ );
+};
diff --git a/packages/ui/src/components/Skeleton/index.ts b/packages/ui/src/components/Skeleton/index.ts
new file mode 100644
index 000000000..910e924d2
--- /dev/null
+++ b/packages/ui/src/components/Skeleton/index.ts
@@ -0,0 +1,17 @@
+export { Skeleton } from "./Skeleton";
+export type { FlowbiteSkeletonRootTheme, FlowbiteSkeletonTheme, SkeletonProps } from "./Skeleton";
+
+export { SkeletonImage } from "./SkeletonImage";
+export type { FlowbiteSkeletonImageTheme, SkeletonImageProps } from "./SkeletonImage";
+
+export { SkeletonVideo } from "./SkeletonVideo";
+export type { FlowbiteSkeletonVideoTheme, SkeletonVideoProps } from "./SkeletonVideo";
+
+export { SkeletonCard } from "./SkeletonCard";
+export type { FlowbiteSkeletonCardTheme, SkeletonCardProps } from "./SkeletonCard";
+
+export { SkeletonList } from "./SkeletonList";
+export type { FlowbiteSkeletonListTheme, SkeletonListProps } from "./SkeletonList";
+
+export { SkeletonTestimonial } from "./SkeletonTestimonial";
+export type { FlowbiteSkeletonTestimonialTheme, SkeletonTestimonialProps } from "./SkeletonTestimonial";
diff --git a/packages/ui/src/components/Skeleton/theme.ts b/packages/ui/src/components/Skeleton/theme.ts
new file mode 100644
index 000000000..8aa4ded15
--- /dev/null
+++ b/packages/ui/src/components/Skeleton/theme.ts
@@ -0,0 +1,71 @@
+import type { FlowbiteSkeletonTheme } from "./Skeleton";
+
+export const skeletonTheme: FlowbiteSkeletonTheme = {
+ root: {
+ base: "max-w-sm animate-pulse",
+ },
+ variant: {
+ base: "block bg-gray-200 dark:bg-gray-700 h-[1.2em]",
+ type: {
+ default: "rounded-sm transform origin-[0_55%] my-0 scale-100 text-[1rem]",
+ rectangular: "h-[60px]",
+ rounded: "h-[60px] rounded-sm",
+ circular: "rounded-[50%] w-10 h-10",
+ },
+ },
+ image: {
+ base: "space-y-8 animate-pulse md:space-y-0 md:space-x-8 rtl:space-x-reverse md:flex md:items-center",
+ imgDiv: "flex items-center justify-center w-full h-48 bg-gray-300 rounded sm:w-96 dark:bg-gray-700",
+ imgSvg: "w-10 h-10 text-gray-200 dark:text-gray-600",
+ texts: {
+ base: "w-full",
+ lineOne: "h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4",
+ lineTwo: "h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[480px] mb-2.5",
+ lineThree: "h-2 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5",
+ lineFour: "h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[440px] mb-2.5",
+ lineFive: "h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[460px] mb-2.5",
+ lineSix: "h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[360px]",
+ },
+ },
+ video: {
+ base: "flex items-center justify-center h-56 max-w-sm bg-gray-300 rounded-lg animate-pulse dark:bg-gray-700",
+ svg: "h-10 w-10 text-gray-200 dark:text-gray-600",
+ },
+ card: {
+ base: "max-w-sm p-4 border border-gray-200 rounded shadow animate-pulse md:p-6 dark:border-gray-700",
+ cardImg: {
+ base: "flex items-center justify-center h-48 mb-4 bg-gray-300 rounded dark:bg-gray-700",
+ svg: "w-10 h-10 text-gray-200 dark:text-gray-600",
+ },
+ text: "h-2 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5",
+ userIcon: {
+ base: "flex items-center mt-4",
+ icon: "w-10 h-10 me-3 text-gray-200 dark:text-gray-700",
+ text: "w-48 h-2 bg-gray-200 rounded-full dark:bg-gray-700",
+ },
+ },
+ list: {
+ base: "max-w-md p-4 space-y-4 border border-gray-200 divide-y divide-gray-200 rounded shadow animate-pulse dark:divide-gray-700 md:p-6 dark:border-gray-700",
+ textList: {
+ base: "flex items-center justify-between pt-4",
+ list: {
+ textOne: "h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5",
+ textTwo: "w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700",
+ textThree: "h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12",
+ },
+ },
+ },
+ testimonial: {
+ base: "animate-pulse",
+ textList: {
+ firstLine: "h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 max-w-[640px] mb-2.5 mx-auto",
+ secondLine: "h-2.5 mx-auto bg-gray-300 rounded-full dark:bg-gray-700 max-w-[540px]",
+ author: {
+ base: "flex items-center justify-center mt-4",
+ userIcon: "w-8 h-8 text-gray-200 dark:text-gray-700 me-4",
+ authorName: "w-20 h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 me-3",
+ secondText: "w-24 h-2 bg-gray-200 rounded-full dark:bg-gray-700",
+ },
+ },
+ },
+};
diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts
index d4ac43525..cc7346fd3 100644
--- a/packages/ui/src/index.ts
+++ b/packages/ui/src/index.ts
@@ -32,6 +32,7 @@ export * from "./components/RangeSlider";
export * from "./components/Rating";
export * from "./components/Select";
export * from "./components/Sidebar";
+export * from "./components/Skeleton";
export * from "./components/Spinner";
export * from "./components/Table";
export * from "./components/Tabs";
diff --git a/packages/ui/src/theme.ts b/packages/ui/src/theme.ts
index a5c64cb0b..170ffa0b8 100644
--- a/packages/ui/src/theme.ts
+++ b/packages/ui/src/theme.ts
@@ -30,6 +30,7 @@ import { rangeSliderTheme } from "./components/RangeSlider/theme";
import { ratingAdvancedTheme, ratingTheme } from "./components/Rating/theme";
import { selectTheme } from "./components/Select/theme";
import { sidebarTheme } from "./components/Sidebar/theme";
+import { skeletonTheme } from "./components/Skeleton/theme";
import { spinnerTheme } from "./components/Spinner/theme";
import { tableTheme } from "./components/Table/theme";
import { tabTheme } from "./components/Tabs/theme";
@@ -77,6 +78,7 @@ export const theme: FlowbiteTheme = {
textarea: textareaTheme,
toggleSwitch: toggleSwitchTheme,
sidebar: sidebarTheme,
+ skeleton: skeletonTheme,
spinner: spinnerTheme,
table: tableTheme,
tabs: tabTheme,