A modern, production-ready UI theme package for Refine applications built with shadcn/ui components, featuring Tailwind CSS v4, React 19, and comprehensive i18n support.
- ๐จ Latest Tailwind CSS v4 with OKLCH color system
- โก React 19 & TypeScript ready
- ๐ Built-in i18n support (English & Turkish)
- ๐ฏ Refine-optimized components
- ๐งฉ Shadcn/ui design system
- ๐ฑ Responsive & Accessible
- ๐ง Fully customizable
# npm
npm install @ferdiunal/refine-shadcn
# pnpm (recommended)
pnpm add @ferdiunal/refine-shadcn
# yarn
yarn add @ferdiunal/refine-shadcn
// In your main application file (App.tsx, index.tsx, etc.)
import "@ferdiunal/refine-shadcn/dist/globals.css";
import { Refine } from "@refinedev/core";
import { BrowserRouter } from "react-router-dom";
import { ThemeProvider } from "@ferdiunal/refine-shadcn";
function App() {
return (
<BrowserRouter>
<ThemeProvider>
<Refine
// ... your Refine configuration
>
{/* Your application */}
</Refine>
</ThemeProvider>
</BrowserRouter>
);
}
All buttons include built-in authorization checks, loading states, and i18n support:
import {
CreateButton,
EditButton,
DeleteButton,
ShowButton,
ListButton
} from "@ferdiunal/refine-shadcn";
function ActionBar() {
return (
<div className="flex gap-2">
<CreateButton resource="posts" />
<EditButton resource="posts" recordItemId="1" />
<ShowButton resource="posts" recordItemId="1" />
<DeleteButton
resource="posts"
recordItemId="1"
confirmTitle="Delete Post?"
confirmDescription="This will permanently delete the post."
/>
<ListButton resource="posts" />
</div>
);
}
- ๐ Vite Template - Complete Vite.js setup
- ๐ Next.js Template - Next.js App Router ready
Type-safe form fields with automatic validation and i18n support:
import { FormField } from "@ferdiunal/refine-shadcn";
import { Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ferdiunal/refine-shadcn/ui";
function UserForm({ control }) {
return (
<div className="space-y-4">
<FormField
control={control}
name="name"
label="Full Name"
description="Enter your full name"
>
{(field) => <Input placeholder="John Doe" {...field} />}
</FormField>
<FormField
control={control}
name="role"
label="User Role"
>
{(field) => (
<Select onValueChange={field.onChange} value={field.value}>
<SelectTrigger>
<SelectValue placeholder="Select role..." />
</SelectTrigger>
<SelectContent>
<SelectItem value="admin">Administrator</SelectItem>
<SelectItem value="user">User</SelectItem>
</SelectContent>
</Select>
)}
</FormField>
</div>
);
}
Built-in support for multiple languages with automatic component translation:
import { Refine } from "@refinedev/core";
import { I18nProvider, locales } from "@ferdiunal/refine-shadcn";
// Quick i18n setup
const i18nProvider = {
translate: (key: string, defaultMessage?: string) => i18n.t(key, defaultMessage),
changeLocale: (lang: string) => i18n.changeLanguage(lang),
getLocale: () => i18n.language,
};
function App() {
return (
<Refine i18nProvider={i18nProvider}>
<I18nProvider>
{/* All components now support i18n automatically */}
<CreateButton /> {/* Automatically translates to "Create" or "Oluลtur" */}
<DeleteButton /> {/* Shows localized confirmation dialogs */}
</I18nProvider>
</Refine>
);
}
Supported Languages: ๐ฌ๐ง English โข ๐น๐ท Turkish
Auto-translated Components:
- โ All action buttons (Create, Edit, Delete, etc.)
- โ Form labels and validation messages
- โ Confirmation dialogs
- โ Table headers and pagination
- โ Loading states and notifications
Feature-rich data tables with filtering, sorting, and bulk actions:
๐ Table Implementation Example
import { ListPage, Table, TableFilterProps } from "@ferdiunal/refine-shadcn";
import { AvatarImage } from "@radix-ui/react-avatar";
import { BaseRecord, HttpError, useUserFriendlyName } from "@refinedev/core";
import type { UseTableReturnType } from "@refinedev/react-table";
import { Edit, Eye, Trash2 } from "lucide-react";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Checkbox } from "@/components/ui/checkbox";
const UserList = () => {
const friendly = useUserFriendlyName();
const bulkDeleteAction = (
table: UseTableReturnType<BaseRecord, HttpError>,
) => {
const label = `Delete Selected (${
table.getSelectedRowModel().rows.length
}) ${friendly(
"Row",
table.getSelectedRowModel().rows.length > 1 ? "plural" : "singular",
)}`;
return {
label,
onClick: () => {
alert("Delete Selected");
},
};
};
return (
<ListPage>
<Table enableSorting enableFilters>
<Table.Column
accessorKey="id"
id={"select"}
header={({ table }) => (
<Table.CheckAll
options={[bulkDeleteAction(table)]}
table={table}
/>
)}
cell={({ row }) => (
<Checkbox
className="translate-y-[2px]"
checked={row.getIsSelected()}
onCheckedChange={(value) =>
row.toggleSelected(!!value)
}
aria-label="Select row"
key={`checkbox-${row.original.id}`}
/>
)}
/>
<Table.Column
header={"ID"}
id="id"
accessorKey="id"
enableSorting
enableHiding
/>
<Table.Column
header={"Avatar"}
id="avatar"
accessorKey="avatar"
cell={({ row }) =>
row.original.avatar?.[0]?.url && (
<Avatar>
<AvatarImage
src={row.original.avatar[0].url}
alt={row.original.avatar[0].name}
/>
<AvatarFallback>
{row.original.firstName[0]}
{row.original.lastName[0]}
</AvatarFallback>
</Avatar>
)
}
/>
<Table.Column
header={"First Name"}
accessorKey="firstName"
id="firstName"
enableSorting
enableHiding
/>
<Table.Column
header={"Last Name"}
accessorKey="lastName"
id="lastName"
enableSorting
enableHiding
/>
<Table.Column
header={"Birthday"}
accessorKey="birthday"
id="birthday"
enableSorting
enableHiding
filter={(props: TableFilterProps) => (
<Table.Filter.DateRangePicker {...props} align="end" />
)}
/>
<Table.Column
accessorKey={"id"}
id={"actions"}
cell={({ row: { original } }) => (
<Table.Actions>
<Table.ShowAction
title="Detail"
row={original}
resource="users"
icon={<Eye size={16} />}
/>
<Table.EditAction
title="Edit"
row={original}
resource="users"
icon={<Edit size={16} />}
/>
<Table.DeleteAction
title="Delete"
row={original}
withForceDelete={true}
resource="users"
icon={<Trash2 size={16} />}
/>
</Table.Actions>
)}
/>
</Table>
</ListPage>
);
};
export default UserList;
Clean, structured display for individual record details:
๐๏ธ Show Page Example
import { ShowPage } from "@ferdiunal/refine-shadcn";
import { IResourceComponentsProps, useShow } from "@refinedev/core";
import { IUser } from "./Form";
const UserShow: React.FC<IResourceComponentsProps> = () => {
const {
query: { data },
} = useShow<IUser>();
const record = data?.data;
return (
<ShowPage>
<ShowPage.Row title="ID" children={record?.id as number} />
<ShowPage.Row
title="First Name"
children={record?.firstName?.toString() || ""}
/>
<ShowPage.Row
title="Last Name"
children={record?.firstName?.toString() || ""}
/>
<ShowPage.Row
title="Email"
children={record?.email?.toString() || ""}
/>
</ShowPage>
);
};
export default UserShow;
User-friendly forms for creating new records with validation:
โ Create Form Example
import { CreatePage } from "@ferdiunal/refine-shadcn";
import { Field, Form } from "@ferdiunal/refine-shadcn";
import { zodResolver } from "@hookform/resolvers/zod";
import { RedirectAction } from "@refinedev/core";
import { useForm } from "@refinedev/react-hook-form";
import * as z from "zod";
import { Input } from "@/components/ui/input";
export interface IUser {
id: number;
firstName: string;
lastName: string;
email: string;
}
const formSchema = z.object({
firstName: z.string().min(2, {
message: "Firstname must be at least 2 characters.",
}),
lastName: z.string().min(2, {
message: "Lastname must be at least 2 characters.",
}),
email: z.string().email({
message: "Please enter a valid email address.",
}),
});
const UserCreate = () => {
const { ...form } = useForm<z.infer<typeof formSchema>>({
mode: "all",
resolver: zodResolver(formSchema),
defaultValues: {
firstName: "",
lastName: "",
email: "",
},
refineCoreProps: {
autoSave: {
enabled: true,
},
redirect,
},
warnWhenUnsavedChanges: true,
});
return (
<CreatePage>
<Form {...form}>
<Field {...form} name="firstName" label="Firstname">
<Input placeholder="Firstname" />
</Field>
<Field {...form} name="lastName" label="Lastname">
<Input placeholder="Lastname" />
</Field>
<Field {...form} name="email" label="Email">
<Input placeholder="email" type="email" />
</Field>
</Form>
</CreatePage>
);
};
export default UserCreate;
Pre-populated forms for updating existing records:
โ๏ธ Edit Form Example
import { EditPage } from "@ferdiunal/refine-shadcn";
import { Field, Form } from "@ferdiunal/refine-shadcn";
import { zodResolver } from "@hookform/resolvers/zod";
import { RedirectAction } from "@refinedev/core";
import { useForm } from "@refinedev/react-hook-form";
import * as z from "zod";
import { Input } from "@/components/ui/input";
export interface IUser {
id: number;
firstName: string;
lastName: string;
email: string;
}
const formSchema = z.object({
firstName: z.string().min(2, {
message: "Firstname must be at least 2 characters.",
}),
lastName: z.string().min(2, {
message: "Lastname must be at least 2 characters.",
}),
email: z.string().email({
message: "Please enter a valid email address.",
}),
});
const UserEdit = () => {
const { ...form } = useForm<z.infer<typeof formSchema>>({
mode: "all",
resolver: zodResolver(formSchema),
defaultValues: {
firstName: "",
lastName: "",
email: "",
},
refineCoreProps: {
autoSave: {
enabled: true,
},
redirect,
},
warnWhenUnsavedChanges: true,
});
return (
<EditPage>
<Form {...form}>
<Field {...form} name="firstName" label="Firstname">
<Input placeholder="Firstname" />
</Field>
<Field {...form} name="lastName" label="Lastname">
<Input placeholder="Lastname" />
</Field>
<Field {...form} name="email" label="Email">
<Input placeholder="email" type="email" />
</Field>
</Form>
</EditPage>
);
};
export default UserEdit;
Access the complete shadcn/ui component library:
import {
Button,
Input,
Card,
Dialog,
Select,
Table,
Form
} from "@ferdiunal/refine-shadcn/ui";
function MyComponent() {
return (
<Card className="p-6">
<Button variant="primary" size="lg">
Click me
</Button>
<Input placeholder="Type here..." />
</Card>
);
}
Override CSS variables to match your brand:
:root {
--primary: oklch(0.7 0.15 250);
--primary-foreground: oklch(0.98 0.02 250);
--secondary: oklch(0.96 0.01 250);
/* Customize all theme tokens */
}
Full type safety across all components:
// Automatic type inference
type UserForm = {
name: string;
email: string;
role: 'admin' | 'user';
};
// Type-safe form fields
<FormField<UserForm, "name">
control={control}
name="name" // โ
Fully typed
label="Full Name"
>
{(field) => <Input {...field} />}
</FormField>
- ๐ Documentation - Complete guide and API reference
- ๐ฎ Live Examples - Interactive demos
- ๐ Issues - Report bugs or request features
- ๐ฌ Discussions - Community support
We welcome contributions! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.
This project is licensed under the MIT License - see the LICENSE file for details.
- Refine - The headless React framework
- shadcn/ui - Beautiful component library
- Tailwind CSS - Utility-first CSS framework
- Radix UI - Accessible component primitives
Made with โค๏ธ by @ferdiunal
โญ Star this repo if it helped you!