npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

klynn-ui

v0.1.0

Published

A copy-paste-friendly, Radix-powered React component library, distributed as an npm package.

Readme

kiln-ui

A copy-paste-friendly React component library, built the way shadcn/ui builds its components — Radix UI primitives underneath, class-variance-authority for variants, Tailwind for styling, a cn() helper for class merging — but packaged as a normal installable npm dependency instead of a CLI-copied source tree.

What's in here

| Component | Pattern it demonstrates | |---|---| | Button | CVA variants + asChild via Radix Slot | | Badge | CVA variants, no Radix dependency | | Input, Textarea, Label | Plain forwardRef wrapper / Radix primitive wrapper | | Card family | Compound component (Header/Title/Description/Content/Footer) | | Avatar family | Radix primitive wrapper with image+fallback | | Separator | Minimal Radix wrapper | | Tooltip family | Radix + Portal, requires TooltipProvider | | Dialog family | Radix + Portal, overlay, focus trap, animated states | | DropdownMenu family | Radix + Portal, items/checkboxes/radio/submenus | | Tabs family | Radix, stateful active-tab styling via data-state | | Checkbox, RadioGroup, Switch | Radix form controls with custom indicator markup | | Select family | Radix Select, Portal, scroll buttons, popper positioning | | Slider | Radix Slider, dynamic thumb count for range selection | | Toggle, ToggleGroup | Shared CVA variants exposed via context | | Alert family | CVA variant box, no Radix dependency | | AlertDialog family | Radix + Portal, non-dismissable confirmation pattern | | Popover, HoverCard | Radix + Portal, positioned floating content | | Accordion | Radix + height-animated open/close via --radix-accordion-content-height | | Collapsible | Minimal 3-line Radix re-export | | Progress | Radix, transform-based indicator | | Skeleton | Plain pulsing placeholder, no Radix dependency | | Table family | Semantic HTML table, styling only | | Sheet family | Dialog primitive + CVA side variants (top/bottom/left/right) | | Toast, Toaster, useToast/toast() | Radix Toast + standalone reducer-based store, called imperatively | | Form family | react-hook-form integration: FormField/FormItem/FormControl wire labels, descriptions, and errors together via ARIA automatically |

Every component is small, readable, and meant to be copied and modified — the same philosophy as shadcn/ui. The difference is kiln-ui is also published so you can just npm install it instead of running a CLI to vendor the source.

Not included (yet): Calendar/DatePicker, Command/Combobox, Carousel, NavigationMenu, Menubar, ContextMenu, Breadcrumb, Pagination, InputOTP, AspectRatio, and a TanStack-powered DataTable. These either pull in a heavier third-party dependency (react-day-picker, cmdk, embla-carousel) or are thin compositions of components already here — happy to add any of them on request.

Project structure

kiln-ui/
  src/
    components/        — one file per component (or component family)
    lib/utils.ts        — cn() helper (clsx + tailwind-merge)
    styles/globals.css  — CSS variable design tokens (light + dark)
    tailwind.preset.ts  — maps the CSS variables onto Tailwind theme keys
    index.ts            — barrel export
  tsup.config.ts        — bundles ESM + CJS + .d.ts, copies globals.css
  package.json

Using it in a consuming app

npm install kiln-ui
npm install -D tailwindcss-animate   # peer of the preset's animations

1. Extend your Tailwind config with the preset:

// tailwind.config.ts
import type { Config } from "tailwindcss";
import kilnPreset from "kiln-ui/tailwind.preset";

export default {
  presets: [kilnPreset],
  content: [
    "./src/**/*.{ts,tsx}",
    "./node_modules/kiln-ui/dist/**/*.{js,mjs}", // <- important, see note below
  ],
} satisfies Config;

Why the node_modules content path matters: Tailwind only generates CSS for class names it can find as literal strings in the files listed under content. Since kiln-ui's components ship as compiled JS containing those class strings, you must point Tailwind at kiln-ui's dist folder or the button/dialog/etc. styles will silently not exist in your final CSS.

2. Import the base CSS once, at your app root:

// app/layout.tsx, src/main.tsx, etc.
import "kiln-ui/styles.css";

3. Use components:

import { Button, Card, CardHeader, CardTitle, CardContent } from "kiln-ui";

export function Example() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>Welcome</CardTitle>
      </CardHeader>
      <CardContent>
        <Button variant="default">Get started</Button>
        <Button variant="outline">Learn more</Button>
      </CardContent>
    </Card>
  );
}

Dialog, DropdownMenu, and Tabs are compound components — import every piece you need:

import {
  Dialog,
  DialogTrigger,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogDescription,
} from "kiln-ui";

<Dialog>
  <DialogTrigger asChild>
    <Button variant="outline">Open</Button>
  </DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Are you sure?</DialogTitle>
      <DialogDescription>This action cannot be undone.</DialogDescription>
    </DialogHeader>
  </DialogContent>
</Dialog>

Tooltip needs a single TooltipProvider near the root of your app (wrap your whole app, not each tooltip):

import { TooltipProvider } from "kiln-ui";

<TooltipProvider>
  <App />
</TooltipProvider>

Toast works like shadcn's original Radix-based toast: mount <Toaster /> once near your app root, then call toast() imperatively from anywhere — no provider wrapping required at the call site, the store is module-level.

// app root
import { Toaster } from "kiln-ui";
<App />
<Toaster />

// anywhere else
import { toast } from "kiln-ui";
toast({ title: "Saved", description: "Your changes were saved." });
toast({ variant: "destructive", title: "Something went wrong" });

Form wraps react-hook-form — install it in your app (it's an optional peer dependency of kiln-ui, not bundled, so there's exactly one copy of it and useForm()'s context matches what Form reads):

npm install react-hook-form
import { useForm } from "react-hook-form";
import {
  Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage,
  Input, Button,
} from "kiln-ui";

function ProfileForm() {
  const form = useForm({ defaultValues: { email: "" } });

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit((values) => console.log(values))}>
        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Email</FormLabel>
              <FormControl>
                <Input placeholder="[email protected]" {...field} />
              </FormControl>
              <FormDescription>We'll never share it.</FormDescription>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button type="submit">Save</Button>
      </form>
    </Form>
  );
}

FormMessage automatically displays validation errors from react-hook-form's field state — pair FormField's name with a Zod/Yup schema via @hookform/resolvers for full validation, or just use RHF's built-in rules/required props.

Theming

All colors are CSS variables in HSL channel form (--primary: 240 5.9% 10%), set in globals.css and consumed by the Tailwind preset as hsl(var(--primary)). To re-theme:

  • Override the variables in your own CSS after importing kiln-ui/styles.css.
  • Or copy src/styles/globals.css into your app and edit the values directly, then skip importing the package's copy.
  • Toggle dark mode by adding/removing the .dark class on <html> (the preset sets darkMode: ["class"]).

Local development

npm install
npm run dev        # tsup --watch
npm run typecheck
npm run build       # outputs to dist/ (esm, cjs, .d.ts, styles.css)

There's no demo app wired up yet. The fastest way to test changes against a real consumer project locally is npm link:

# in kiln-ui/
npm run build
npm link

# in your test app
npm link kiln-ui

(Or use npm/pnpm workspaces if you keep the test app in the same monorepo.)

Publishing to npm

  1. Update "name" in package.jsonkiln-ui is almost certainly taken; either rename the package or publish under a scope you own, e.g. @yourname/kiln-ui (set "publishConfig": { "access": "public" }, which is already in place, for scoped public packages).
  2. Bump "version" (semver).
  3. npm login if you haven't already.
  4. npm publishprepublishOnly automatically runs typecheck and build first, and the files field ensures only dist/ ships (no source, no config files).

Adding a new component (following the same pattern)

  1. Create src/components/my-component.tsx.
  2. For a component with visual variants (size/color/intent), define a cva(...) call like in button.tsx and wire it through VariantProps.
  3. For anything interactive/accessible (menus, dialogs, toggles, tooltips), check if Radix has a primitive first (@radix-ui/react-*) and wrap it — don't reimplement focus management, ARIA, or portals by hand.
  4. Always React.forwardRef so consumers can pass refs through, and always run incoming className through cn(...) last so consumer overrides win.
  5. Export the component (and its variant function, if any) from src/index.ts.
  6. Run npm run typecheck && npm run build to confirm it compiles cleanly.

License

MIT