How to Use shadcn/ui Components (Button, Input, Dialog) in refine-shadcn

Import Button, Input, Dialog, and other shadcn/ui primitives directly from @ferdiunal/refine-shadcn/ui to use them with built-in theme awareness and Refine-specific defaults.

The ferdiunal/refine-shadcn repository provides a thin, opinionated wrapper around original shadcn/ui components, enabling seamless integration with the Refine framework. These components ship with Tailwind-based styling, Radix UI primitives, and built-in support for Refine's form handling and theming systems.

Import Pattern for shadcn/ui Components

All underlying shadcn/ui primitives are re-exported through the @ferdiunal/refine-shadcn/ui namespace. The package's exports field in packages/theme/package.json maps the "./ui" entry to compiled distribution files generated from packages/theme/src/ui/index.ts.

Import components directly from the UI entry point:

import { Button, Input, Dialog } from "@ferdiunal/refine-shadcn/ui";

This single entry point exposes the full API surface of Button, Input, Dialog, and additional shadcn/ui primitives without requiring separate installations or internal path references.

Component Architecture and Implementation

Each component in packages/theme/src/ui/ implements a consistent pattern: Radix UI primitives wrapped with class-variance-authority (cva) for variant management and the cn utility function for conditional class merging.

Button Component

The Button implementation in packages/theme/src/ui/button.tsx extends the base Radix primitive with Tailwind styling and variant configurations. It supports standard shadcn/ui variants (default, destructive, outline, secondary, ghost, link) plus Refine-specific enhancements for loading states and disabled handling.

Key implementation details:

Input Component

Located at packages/theme/src/ui/input.tsx, the Input wrapper provides a thin abstraction over the native HTML <input> element. It forwards all standard attributes while applying consistent styling for focus states, borders, and background colors through the Tailwind configuration.

Dialog Component

The Dialog family in packages/theme/src/ui/dialog.tsx implements a full modal system using @radix-ui/react-dialog. This includes DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, and DialogClose components.

The implementation adds:

  • Built-in animations via Tailwind transitions
  • Optional close button visibility via the showCloseButton prop
  • Backdrop styling consistent with the theme provider

Practical Usage Examples

Basic Button with Variants

Create styled buttons using the variant API defined in the source:

import { Button } from "@ferdiunal/refine-shadcn/ui";

export const ActionButton = () => (
  <Button
    variant="default"
    size="lg"
    onClick={() => console.log("Action triggered")}
  >
    Execute Action
  </Button>
);

The variant prop accepts values mapped through the cva configuration in button.tsx, while size controls padding and height dimensions.

Input Integration with Refine Forms

Combine Input with Refine's FormField for type-safe form handling:

import { Input } from "@ferdiunal/refine-shadcn/ui";
import { FormField } from "@ferdiunal/refine-shadcn";

export const EmailField = ({ control }) => (
  <FormField
    control={control}
    name="email"
    label="Email Address"
    description="Enter your business email"
  >
    {(field) => (
      <Input 
        type="email" 
        placeholder="user@company.com" 
        {...field} 
      />
    )}
  </FormField>
);

The Input component forwards all React input props, ensuring compatibility with React Hook Form's field handlers.

Complete Dialog Implementation

Build accessible modals using the full Dialog compound component pattern:

import {
  Dialog,
  DialogTrigger,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogDescription,
  DialogFooter,
  DialogClose,
} from "@ferdiunal/refine-shadcn/ui";

export const ConfirmDialog = () => (
  <Dialog>
    <DialogTrigger asChild>
      <Button variant="outline">Delete Item</Button>
    </DialogTrigger>

    <DialogContent showCloseButton>
      <DialogHeader>
        <DialogTitle>Confirm Deletion</DialogTitle>
        <DialogDescription>
          This action cannot be undone. The item will be permanently removed from your account.
        </DialogDescription>
      </DialogHeader>

      <DialogFooter>
        <DialogClose asChild>
          <Button variant="secondary">Cancel</Button>
        </DialogClose>
        <Button variant="destructive" onClick={handleDelete}>
          Delete
        </Button>
      </DialogFooter>
    </DialogContent>
  </Dialog>
);

The showCloseButton prop toggles the visibility of the upper-right close control, while asChild on triggers and closes ensures proper composition with button elements.

Combined Example in a Refine Action

Use all three components together in a create or edit form wrapped in a dialog:

import { Button, Input, Dialog } from "@ferdiunal/refine-shadcn/ui";
import { FormField } from "@ferdiunal/refine-shadcn";
import { useForm } from "@refinedev/react-hook-form";

export const CreateRecordModal = () => {
  const { control, handleSubmit } = useForm();

  const onSubmit = (data) => console.log(data);

  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button>Create New Record</Button>
      </DialogTrigger>

      <DialogContent>
        <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
          <FormField 
            control={control} 
            name="title" 
            label="Record Title"
          >
            {(field) => <Input placeholder="Enter title..." {...field} />}
          </FormField>

          <FormField 
            control={control} 
            name="identifier" 
            label="Identifier"
          >
            {(field) => <Input placeholder="ID-123" {...field} />}
          </FormField>

          <div className="flex justify-end gap-2 pt-4">
            <DialogClose asChild>
              <Button variant="outline">Cancel</Button>
            </DialogClose>
            <Button type="submit">Save Record</Button>
          </div>
        </form>
      </DialogContent>
    </Dialog>
  );
};

Summary

  • Import from @ferdiunal/refine-shadcn/ui to access all shadcn/ui primitives compiled from packages/theme/src/ui/
  • Components wrap Radix UI primitives with Tailwind styling via cva and the cn utility
  • Source files for Button, Input, and Dialog live in packages/theme/src/ui/button.tsx, input.tsx, and dialog.tsx respectively
  • Theme awareness is built-in through integration with the ThemeProvider
  • Refine compatibility includes loading states, form field forwarding, and disabled handling patterns

Frequently Asked Questions

Do I need to install shadcn/ui separately when using refine-shadcn?

No, all shadcn/ui components are bundled within the @ferdiunal/refine-shadcn package. The components are compiled by tsup from packages/theme/src/ui/ into the distribution folder and exposed through the "./ui" export in packages/theme/package.json. Importing from @ferdiunal/refine-shadcn/ui provides the same API without additional dependencies.

Can I customize the Tailwind classes for these components?

Yes, all components accept a className prop that gets merged with base styles through the cn utility function. The source files use class-variance-authority (cva) for variant definitions, allowing you to override or extend the default Tailwind classes. You can also modify the base components directly in packages/theme/src/ui/ before compilation.

Are Radix UI primitives exposed directly?

The underlying Radix UI primitives (@radix-ui/react-dialog, @radix-ui/react-slot, etc.) are used internally but not re-exported directly. Instead, they are wrapped with Refine-specific logic and Tailwind styling. If you need direct access to Radix primitives, you would install them separately, though the wrapped versions in refine-shadcn provide all standard Radix functionality plus theme integration.

How does theme switching work with these components?

Components automatically respond to the ThemeProvider defined in packages/theme/src/providers/theme-provider.tsx. The provider manages dark/light mode state and CSS variable injection. Because components use Tailwind's dark mode modifiers and the cn utility for class merging, they react dynamically to theme changes without requiring manual configuration per component.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →