DEV Community

Cover image for Building React Components Compatible with shadcn/ui CLI
Keita M
Keita M

Posted on

Building React Components Compatible with shadcn/ui CLI

Recently, I participated as a partner in the Builders Weekend hackathon, which inspired me to develop a React component that works with the shadcn/ui CLI. Iโ€™d like to share the step-by-step process and some of the highlights!

What I made

Agents Kit helps you build AI Chat apps instantly with Vercel AI SDK.

https://agents-kit.dev/

import * as React from "react"
import { useChat } from '@ai-sdk/react'
import { ChatForm } from '@/components/agents-kit/chat-form';

export function ChatFormDemo() {
  const { input, handleInputChange, handleSubmit } = useChat({});

  return (
    <ChatForm 
      inputValue={input} 
      onInputChange={handleInputChange} 
      onSubmit={handleSubmit} 
    />
  )
}
Enter fullscreen mode Exit fullscreen mode

TL;DR

How to Develop

  1. Start a Next.js project. Develop the React component you want to distribute in the /registry directory.
  2. Write a registry.json once your component is complete to configure its distribution.
  3. Run shadcn build to build a JSON file for each component. The files will be output to the public directory.
  4. Deploy your Next.js project to publish the JSON files. It is recommended to develop your Next.js project as the documentation site itself.

Image description

Highlights

  • You can develop it as a regular React project. You can get started easily since thereโ€™s no need to bundle it as a component library.
  • Dependency management is great. Itโ€™s convenient that, upon installing a component, the necessary npm packages and shadcn/ui components are automatically installed.

How to build

1. Initialize a React Project and Develop Your Component

Start a React project using Next.js or Vite. Developing components for the shadcn/ui CLI follows the same straightforward process as with any regular React project.

Below is an example of a component called ChatForm, which is actually distributed as part of Agents Kit.

// registry/agents-kit/chat-form.tsx
"use client";

import React, { FC, KeyboardEvent, PropsWithChildren, useRef } from "react";
import { AutosizeTextarea } from "./autosize-textarea";
import { ChatActionIconButton, ChatActions } from "./chat-actions";
import { Button } from "@/components/ui/button";
import { LucideArrowUp, LucidePlus } from "lucide-react";
import { Card } from "@/components/ui/card";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";

export interface ChatFormProps {
  inputValue?: string;
  onInputChange?: (
    e:
      | React.ChangeEvent<HTMLInputElement>
      | React.ChangeEvent<HTMLTextAreaElement>
  ) => void;
  onFileChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
  onSubmit?: (e: React.FormEvent<HTMLFormElement>) => void;
  actions?: React.ReactNode;
}

const ChatForm: FC<PropsWithChildren<ChatFormProps>> = (props) => {
  const fileInputRef = useRef<HTMLInputElement>(null);

  const submitButtonRef = useRef<HTMLButtonElement>(null);

  const handleKeyDown = React.useCallback(
    (e: KeyboardEvent<HTMLTextAreaElement>) => {
      if (e.metaKey && e.key === "Enter") {
        submitButtonRef.current?.click();
      }
    },
    []
  );

  return (
    <Card className="p-2 rounded-3xl shadow">
      <form onSubmit={props.onSubmit} className="grid grid-cols-1 gap-2">
        {props.children}
        <AutosizeTextarea
          value={props.inputValue}
          placeholder="Type a message..."
          className="resize-none"
          variant={"ghost"}
          onChange={props.onInputChange}
          maxHeight={260}
          onKeyDown={handleKeyDown}
        />
        <ChatActions>
          {props.onFileChange && (
            <>
              <DropdownMenu>
                <DropdownMenuTrigger>
                  <ChatActionIconButton>
                    <LucidePlus />
                  </ChatActionIconButton>
                </DropdownMenuTrigger>
                <DropdownMenuContent>
                  <DropdownMenuItem
                    onClick={() => fileInputRef.current?.click()}
                  >
                    Upload from computer
                  </DropdownMenuItem>
                </DropdownMenuContent>
              </DropdownMenu>
              <input
                ref={fileInputRef}
                onChange={props.onFileChange}
                type="file"
                className="hidden"
              />
            </>
          )}
          {props.actions}
          <div className="flex-1" />
          <Button
            ref={submitButtonRef}
            type="submit"
            size="icon"
            className="rounded-full"
          >
            <LucideArrowUp />
          </Button>
        </ChatActions>
      </form>
    </Card>
  );
};

export { ChatForm };
Enter fullscreen mode Exit fullscreen mode

2. Writing registry.json

Create a registry.json file at the project root and configure it using the documentation as a reference. The key points are as follows:

  • List npm package dependencies under dependencies
  • List shadcn/ui CLI-compatible dependencies under registryDependencies

With registryDependencies, you can manage dependencies on shadcn/ui components and your own shadcn/ui CLI compatible components.

{
    "$schema": "https://ui.shadcn.com/schema/registry.json",
    "name": "Agents Kit",
    "homepage": "https://agents-kit.dev",
    "items": [
        ...
        {
            "name": "chat-form",
            "type": "registry:component",
            "title": "Chat Form",
            "description": "A chat form component.",
            "dependencies": ["lucide-react"],
            "registryDependencies": [
              "card", 
              "dropdown-menu", 
              "https://agents-kit.dev/r/autosize-textarea.json", 
              "https://agents-kit.dev/r/chat-actions.json"
            ],
            "files": [
                {
                    "path": "src/registry/agents-kit/chat-form.tsx",
                    "type": "registry:component",
                    "target": "components/agents-kit/chat-form.tsx"
                }
            ]
        },
        ...
    ]
}
Enter fullscreen mode Exit fullscreen mode

3. Build component.json

Run the shadcn build command to build the component.json file. In this example, public/r/chat-form.json is generated.

{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "chat-form",
  "type": "registry:component",
  "title": "Chat Form",
  "description": "A chat form component.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "card",
    "dropdown-menu",
    "https://agents-kit.dev/r/autosize-textarea.json",
    "https://agents-kit.dev/r/chat-actions.json"
  ],
  "files": [
    {
      "path": "src/registry/agents-kit/chat-form.tsx",
      "content": "\"use client\";\n\n ...",
      "type": "registry:component",
      "target": "components/agents-kit/chat-form.tsx"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Once the project is deployed and JSON file is published, you can run a command as the following:

npx shadcn@latest add http://agents-kit.dev/r/chat-form.json
Enter fullscreen mode Exit fullscreen mode

4. Developing React project as a documentation website

This completes the distribution process! Since you need to deploy the project anyway to publish the component.json, I recommend developing your React project as the documentation site.


Highlights

Streamlined Dependency Management

The registry.json file manages npm packages and other component.json dependencies, simplifying development and distribution. Dependencies are automatically installed, allowing you to distribute components without publishing them on npm.

Effortless Team Collaboration

By bypassing npm publishing, you can quickly share and maintain a component library within your team. This approach offers all the essential features while being more convenient than alternatives like npm or GitHub Packages.

Top comments (1)

Collapse
 
robin-ivi profile image
Robin ๐ŸŽญ

You did it, Keita M! ๐ŸŽŠ
Your first article is live, and we couldnโ€™t be more excited! Keep the creativity flowing, and letโ€™s build something wonderful together. โœ๏ธ๐Ÿ˜Š