Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/bright-buttons-ghost.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"flowbite-react": minor
---

Add a `ghost` button variant that works with the existing button colors.
8 changes: 8 additions & 0 deletions apps/storybook/src/Button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,11 @@ DefaultButton.storyName = "Default";
DefaultButton.args = {
children: "Button",
};

export const GhostButton = Template.bind({});
GhostButton.storyName = "Ghost";
GhostButton.args = {
children: "Ghost button",
color: "green",
ghost: true,
};
6 changes: 6 additions & 0 deletions apps/web/content/docs/components/button.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ Use the `outline` property to create an outline button with transparent backgrou

<Example name="button.outline" />

## Ghost buttons

Use the `ghost` property to create a button with a transparent background and no visible border. Combine it with the `color` property to keep the same semantic color while showing a subtle background on hover.

<Example name="button.ghost" />

## Button sizes

You can update the size of the button by adding the `size` property to the `<Button>` component.
Expand Down
63 changes: 63 additions & 0 deletions apps/web/examples/button/button.ghost.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Button } from "flowbite-react";
import type { CodeData } from "~/components/code-demo";

const code = `
import { Button } from "flowbite-react";

export function Component() {
return (
<div className="flex flex-wrap gap-2">
<Button ghost>Default</Button>
<Button color="dark" ghost>
Dark
</Button>
<Button color="green" ghost>
Green
</Button>
<Button color="red" ghost>
Red
</Button>
<Button color="yellow" ghost>
Yellow
</Button>
<Button color="purple" ghost>
Purple
</Button>
</div>
);
}
`;

export function Component() {
return (
<div className="flex flex-wrap gap-2">
<Button ghost>Default</Button>
<Button color="dark" ghost>
Dark
</Button>
<Button color="green" ghost>
Green
</Button>
<Button color="red" ghost>
Red
</Button>
<Button color="yellow" ghost>
Yellow
</Button>
<Button color="purple" ghost>
Purple
</Button>
</div>
);
}

export const ghost: CodeData = {
type: "single",
code: {
fileName: "index",
language: "tsx",
code,
},
githubSlug: "button/button.ghost.tsx",
component: <Component />,
};
1 change: 1 addition & 0 deletions apps/web/examples/button/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { disabled } from "./button.disabled";
export { ghost } from "./button.ghost";
export { gradientDuo } from "./button.gradientDuo";
export { gradientMono } from "./button.gradientMono";
export { iconOnly } from "./button.iconOnly";
Expand Down
39 changes: 39 additions & 0 deletions packages/ui/src/components/Button/Button.ghost.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { ThemeProvider } from "../../theme/provider";
import type { CustomFlowbiteTheme } from "../../types";
import { Button } from "./Button";

describe("Components / Button / ghost", () => {
it("should use ghost color classes instead of solid color classes", () => {
const theme: CustomFlowbiteTheme = {
button: {
color: {
red: "bg-red-700",
},
ghostColor: {
red: "text-red-700 hover:bg-red-100",
},
},
};

render(
<ThemeProvider theme={theme}>
<Button color="red" ghost>
Delete
</Button>
</ThemeProvider>,
);

const ghostButton = screen.getByRole("button", { name: "Delete" });

expect(ghostButton).toHaveClass("text-red-700", "hover:bg-red-100");
expect(ghostButton).not.toHaveClass("bg-red-700");
});

it("should not forward the ghost prop to the DOM", () => {
render(<Button ghost>Ghost</Button>);

expect(screen.getByRole("button", { name: "Ghost" })).not.toHaveAttribute("ghost");
});
});
5 changes: 4 additions & 1 deletion packages/ui/src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface ButtonTheme {
size: ButtonSizes;
// colors
color: ButtonColors;
ghostColor: ButtonColors;
outlineColor: ButtonOutlineColors;
}

Expand All @@ -51,6 +52,7 @@ export type ButtonProps<T extends ElementType = "button"> = PolymorphicComponent
href?: string;
color?: DynamicStringEnumKeysOf<ButtonColors>;
fullSized?: boolean;
ghost?: boolean;
outline?: boolean;
pill?: boolean;
size?: DynamicStringEnumKeysOf<ButtonSizes>;
Expand All @@ -76,6 +78,7 @@ export const Button = forwardRef(<T extends ElementType = "button">(props: Butto
color = "default",
disabled,
fullSized,
ghost,
outline: _outline,
pill: _pill,
size = "md",
Expand All @@ -97,7 +100,7 @@ export const Button = forwardRef(<T extends ElementType = "button">(props: Butto
pill && theme.pill,
disabled && theme.disabled,
fullSized && theme.fullSized,
outline ? theme.outlineColor[color] : theme.color[color],
ghost ? theme.ghostColor[color] : outline ? theme.outlineColor[color] : theme.color[color],
buttonGroup && theme.grouped,
className,
)}
Expand Down
24 changes: 24 additions & 0 deletions packages/ui/src/components/Button/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,30 @@ export const buttonTheme = createTheme<ButtonTheme>({
yellow:
"bg-yellow-400 text-white hover:bg-yellow-500 focus:ring-yellow-300 dark:bg-yellow-600 dark:hover:bg-yellow-400 dark:focus:ring-yellow-900",
},
ghostColor: {
default:
"bg-transparent text-primary-700 hover:bg-primary-100 focus:ring-primary-300 dark:text-primary-500 dark:hover:bg-primary-900/50 dark:focus:ring-primary-800",
alternative:
"bg-transparent text-gray-900 hover:bg-gray-100 focus:ring-gray-100 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-700",
blue: "bg-transparent text-blue-700 hover:bg-blue-100 focus:ring-blue-300 dark:text-blue-500 dark:hover:bg-blue-900/50 dark:focus:ring-blue-800",
cyan: "bg-transparent text-cyan-700 hover:bg-cyan-100 focus:ring-cyan-300 dark:text-cyan-500 dark:hover:bg-cyan-900/50 dark:focus:ring-cyan-800",
dark: "bg-transparent text-gray-800 hover:bg-gray-100 focus:ring-gray-300 dark:text-gray-300 dark:hover:bg-gray-700 dark:focus:ring-gray-700",
gray: "bg-transparent text-gray-700 hover:bg-gray-100 focus:ring-gray-300 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-800",
green:
"bg-transparent text-green-700 hover:bg-green-100 focus:ring-green-300 dark:text-green-500 dark:hover:bg-green-900/50 dark:focus:ring-green-800",
indigo:
"bg-transparent text-indigo-700 hover:bg-indigo-100 focus:ring-indigo-300 dark:text-indigo-400 dark:hover:bg-indigo-900/50 dark:focus:ring-indigo-800",
light:
"bg-transparent text-gray-500 hover:bg-gray-100 focus:ring-gray-100 dark:text-gray-300 dark:hover:bg-gray-700 dark:focus:ring-gray-700",
lime: "bg-transparent text-lime-700 hover:bg-lime-100 focus:ring-lime-300 dark:text-lime-500 dark:hover:bg-lime-900/50 dark:focus:ring-lime-800",
pink: "bg-transparent text-pink-700 hover:bg-pink-100 focus:ring-pink-300 dark:text-pink-500 dark:hover:bg-pink-900/50 dark:focus:ring-pink-800",
purple:
"bg-transparent text-purple-700 hover:bg-purple-100 focus:ring-purple-300 dark:text-purple-400 dark:hover:bg-purple-900/50 dark:focus:ring-purple-800",
red: "bg-transparent text-red-700 hover:bg-red-100 focus:ring-red-300 dark:text-red-500 dark:hover:bg-red-900/50 dark:focus:ring-red-900",
teal: "bg-transparent text-teal-700 hover:bg-teal-100 focus:ring-teal-300 dark:text-teal-400 dark:hover:bg-teal-900/50 dark:focus:ring-teal-800",
yellow:
"bg-transparent text-yellow-500 hover:bg-yellow-100 focus:ring-yellow-300 dark:text-yellow-300 dark:hover:bg-yellow-900/50 dark:focus:ring-yellow-900",
},
outlineColor: {
default:
"border border-primary-700 text-primary-700 hover:border-primary-800 hover:bg-primary-800 hover:text-white focus:ring-primary-300 dark:border-primary-600 dark:text-primary-500 dark:hover:border-primary-700 dark:hover:bg-primary-700 dark:hover:text-white dark:focus:ring-primary-800",
Expand Down
Loading