Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

Back to skills

Shadcn Web

ASecurity

How to build and maintain shadcn UI + Tailwind v4 web packages in the OwlMeans framework. Auto-invoked when editing components.json, tailwind.config.*, globals.css, or files under components/ui/. Use when creating a new shadcn-based web package or adding shadcn components.

3 stars
0 votes
0 copies
0 views
Added 9/22/2026
developmentjavascripttypescriptgojavashellreactnodetestinggitapi

Works with

cliapi

Security Analysis

A100/100

Scanned 9/22/2026

Install to Claude Code

$npx -y skills add owlmeans/common --skill shadcn-web --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Shadcn Web?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Shadcn Web
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/owlmeans-shadcn-web-common/badge)](https://www.skillsdirectory.com/skills/owlmeans-shadcn-web-common)

More formats (shields.io, HTML) on the badges page.

Download Zip
Files
SKILL.md
---
name: shadcn-web
description: How to build and maintain shadcn UI + Tailwind v4 web packages in the OwlMeans framework. Auto-invoked when editing components.json, tailwind.config.*, globals.css, or files under components/ui/. Use when creating a new shadcn-based web package or adding shadcn components.
user-invocable: false
metadata:
  scope: general
---
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->

# shadcn UI + Tailwind v4 web packages — OwlMeans pattern

Shadcn-based web packages are the **current Web layer**. `@owlmeans/web-panel` is the reference one, and `@owlmeans/mui-panel` is the legacy MUI family it supersedes. Both sit at the same architecture layer (Web) and wrap the same headless `@owlmeans/client-panel` logic; the shadcn family swaps MUI + Emotion for shadcn UI + Tailwind CSS v4, and a new package follows `web-panel`'s shape.

See `reference.md` in this skill folder for full code examples (components.json, tsconfig, globals.css @theme tokens, peerDeps, cn(), MUI→shadcn mapping table).

## Package-boundary rule

Four invariants govern every shadcn-based OwlMeans package:

1. **No shadcn registries.** Primitives (Button, Input, Card, …) are **hand-copied** into `src/@/components/ui/` and committed. The `registries` field in `components.json` is always empty/absent.

2. **Package-local relative imports.** Components import their private shadcn primitives and utilities with relative specifiers. The emitted `build/` tree must contain no absolute `@/…` import: that alias belongs to the consumer and makes a clean installation depend on files it does not own.

3. **Private copy, public facade.** Each package keeps its own copy under `src/@/`, emits it under `build/@/`, and does not expose it through `exports`. Consumers import the package's public `cn` function rather than any private helper.

4. **The consumer must add an `@source` for the package's `src`.** Tailwind's oxide scanner reads
   the CSS root plus `@source` directives only, and it excludes `node_modules` — so a class that
   exists **only** inside the package's own components never reaches the app's stylesheet, and the
   feature renders unstyled with nothing in the app's own sources to blame. Every app consuming
   `@owlmeans/web-panel` (or any other shadcn OwlMeans package) adds a line to its Tailwind entry:

   ```css
   @import "tailwindcss";

   @source "../../../node_modules/@owlmeans/web-panel/src";
   ```

   The relative depth follows the app's layout. Source ships in the package tarball and remains
   tracked in a linked workspace. This is a **general consumer rule**, not a scaffolding detail.

## Package skeleton (mirrors `web-panel`)

```
<your-package>/
├── src/
│   ├── @/                              # private primitives — NOT a public export
│   │   ├── components/ui/              # hand-copied shadcn primitives
│   │   │   ├── button.tsx
│   │   │   └── input.tsx
│   │   ├── lib/
│   │   │   └── utils.ts               # cn() utility
│   │   └── globals.css                # Tailwind v4 entry (app provides its own in prod)
│   ├── components/                    # OwlMeans framework components (wrap client-panel)
│   │   ├── form/
│   │   │   ├── component.tsx
│   │   │   ├── text/component.tsx
│   │   │   ├── button/component.tsx
│   │   │   ├── types.ts
│   │   │   └── index.ts
│   │   ├── layout/component.tsx
│   │   ├── panel-app/component.tsx
│   │   └── index.ts                   # local component barrel only
│   ├── context.ts                     # makeContext wrapping web-client's makeContext
│   ├── main.tsx                       # render() — no theme arg (theme is pure CSS)
│   ├── types.ts
│   ├── entrypoints.ts                 # the base declaration list an app composes over
│   ├── utils.ts                       # the package-owned `cn`, exported publicly
│   └── index.ts                       # package barrel; also re-exports @owlmeans/client-panel
├── tests/
│   ├── harness/
│   │   ├── index.html                 # copy from @owlmeans/test-ui harness/index.html
│   │   └── mount.tsx                  # per-package: imports globals.css, registers components
│   ├── context.ts                     # boots Vite with @tailwindcss/vite + @ alias
│   └── *.spec.ts
├── components.json
├── package.json
└── tsconfig.json
```

## tsconfig setup

```json
{
  "extends": [
    "@owlmeans/dep-config/tsconfig.base.json",
    "@owlmeans/dep-config/tsconfig.react.json"
  ],
  "compilerOptions": {
    "paths": { "@/*": ["./src/@/*"] },
    "rootDir": "./src/",
    "outDir": "./build/"
  },
  "exclude": ["./dist/**/*", "./build/**/*", "./tests/**/*", "./*.ts"]
}
```

`paths` supports shadcn tooling while copying primitives, but package implementation imports are
rewritten to relative specifiers before build. Check emitted JavaScript too: no `from '@/…'` or
`from "@/…"` may remain.

## components.json

```json
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "new-york",
  "rsc": false,
  "tsx": true,
  "tailwind": {
    "config": "",
    "css": "src/@/globals.css",
    "baseColor": "neutral",
    "cssVariables": true,
    "prefix": ""
  },
  "iconLibrary": "lucide",
  "aliases": {
    "components": "@/components",
    "ui": "@/components/ui",
    "utils": "@/lib/utils",
    "lib": "@/lib",
    "hooks": "@/hooks"
  }
}
```

`tailwind.config: ""` is required for Tailwind v4 (no separate config file). Do not add a `registries` field.

## Tailwind v4 wiring

`src/@/globals.css` — the Tailwind entry used in dev/test only:

```css
@import "tailwindcss";

@source "../..";

@theme {
  --color-background: oklch(1 0 0);
  --color-foreground: oklch(0.145 0 0);
  --color-primary: oklch(0.205 0 0);
  --color-primary-foreground: oklch(0.985 0 0);
  --color-destructive: oklch(0.577 0.245 27.325);
  --color-border: oklch(0.922 0 0);
  --color-input: oklch(0.922 0 0);
  --color-ring: oklch(0.708 0 0);
  --radius: 0.625rem;
}
```

`@source "../.."` scans the package root. In the Vite dev/test harness, `@tailwindcss/vite` processes this CSS. The **production app owns its own `globals.css`** with its own `@theme` tokens — the package's CSS is only for tests.

## Adding a primitive without a registry

1. Find the component source on the [shadcn GitHub](https://github.com/shadcn-ui/ui) (e.g. `registry/new-york/ui/<name>.tsx`) or via `npx shadcn@latest add <name> --cwd <your-package-dir>` in a throwaway branch.
2. Copy the `.tsx` source into `src/@/components/ui/<name>.tsx`.
3. Repoint all imports to relative paths into `src/@/lib` and `src/@/components/ui`.
4. Add any `@radix-ui/*` packages the file imports as **peerDependencies** in `package.json`.
5. Add a comment at the top: `// shadcn <name> — sourced from shadcn@<version> <date>`.
6. Run `bun install` and `bun run build` to verify.
7. Document the package's peer dependencies. Consumers never vendor its private primitives;
   `@owlmeans/web-panel` currently needs the Radix peers declared in its manifest.

Prefer a light custom component over a heavyweight block when only part of it is needed. The
two-level navigation shell deliberately renders its second level with the existing `Button` rather
than pulling in the shadcn `sidebar` block: that block drags in `sheet`, `tooltip`, `skeleton` and
`use-mobile`, plus eight `--color-sidebar-*` tokens every consumer theme would then have to define.

## Wrapping `@owlmeans/client-panel`

Shadcn form/field components are the rendering layer over the framework-agnostic headless logic
from `@owlmeans/client-panel`: `FormContext`, `schemaToFormDefault`, `useClientFormContext`,
`useFormI18n` and `useFormError` come from there, while the field itself drives `react-hook-form`'s
`Controller` and resolves its own label from the form namespace. `@owlmeans/client-panel` also
publishes ready headless controllers — `ClientForm`, `InputCtrl`, `ActionCtrl` — but the reference
package does **not** route through them; an application may still use them directly to lay a form
out its own way. The same headless logic is rendered by the MUI family in `@owlmeans/mui-panel`:

```tsx
// MUI version (mui-panel) — for reference
import { TextField } from '@mui/material'
// ...
<TextField {...field} label={label} error={fieldState.error != null} />

// shadcn package implementation (from src/components/form/text/component.tsx)
import { Input } from '../../../@/components/ui/input.js'
import { Label } from '../../../@/components/ui/label.js'
// ...
<div className="flex flex-col gap-1.5">
  <Label htmlFor={field.name}>{label}</Label>
  <Input id={field.name} {...field} aria-invalid={fieldState.error != null} />
  {fieldState.error && (
    <p className="text-sm text-destructive">{fieldState.error.message}</p>
  )}
</div>
```

Both families share the same machinery: `FormProvider`, `useFormContext` and `Controller` from
`react-hook-form`, the AJV validation resolver, and `useFormI18n` from `@owlmeans/client-panel`.
Only the rendered JSX differs. (`@owlmeans/client-i18n` publishes `useI18n`, `useI18nLib`,
`useI18nApp`, `useLanguage`, `composePrefix` and `I18nContext` — the *form*-scoped `t` is
`client-panel`'s.)

`panel-app/component.tsx` needs no MUI `ThemeProvider` and no `CssBaseline` — it renders children, Tailwind's Preflight replaces the baseline, and the theme comes from the app's own `globals.css`.

## package.json exports

```json
{
  "exports": {
    ".": {
      "import": "./build/index.js",
      "types": "./build/index.d.ts"
    },
    "./auth": {
      "import": "./build/auth/index.js",
      "types": "./build/auth/index.d.ts"
    }
  }
}
```

Do **not** add `./@/*` or `./src/@/*` to exports.

## Dev/test harness (Vite + @tailwindcss/vite)

See `[[testing-ui]]` for the full Playwright harness pattern. For shadcn packages the `tests/context.ts` Vite config adds the Tailwind plugin and the `@` alias:

```ts
import tailwindcss from '@tailwindcss/vite'
import { resolve } from 'node:path'

const server = await createServer({
  configFile: false,
  root: resolve(here, './harness'),
  plugins: [react(), tailwindcss()],
  resolve: {
    alias: { '@': resolve(here, '../src/@') },
  },
  server: { port: 0 },
})
```

`tests/harness/mount.tsx` imports the package globals.css so components render with Tailwind styles:

```tsx
import '../../src/@/globals.css'
// ... dynamic component mounting ...
```

## Cross-references

- `[[client-panel]]` — headless form/layout logic being wrapped
- `[[web-panel]]` — the reference shadcn package: the structural and API model to copy
- `[[mui-panel]]` — the legacy MUI family this one supersedes; the source side of the mapping table
- `[[testing-ui]]` — Playwright harness for component acceptance tests
- `[[shadcn-versions]]` — bumping tailwind/shadcn external deps across packages
- `[[tsconfig]]` — OwlMeans TypeScript config conventions
- `[[bun]]` — build, install, workspace filter scripts

Attribution

owlmeansowlmeans
View sourceMore from owlmeans →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Browser Extension Developer

Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content scripts, background scripts, or i18n updates.

284072 votes

Seo Optimizer

SEO optimization with keyword analysis, readability assessment, technical validation, content quality. Use for search rankings, blog posts, content audits, or encountering keyword density, readability scores, meta tags, schema markup errors.

2192 votes

Google Official Seo Guide

Official Google SEO guide covering search optimization, best practices, Search Console, crawling, indexing, and improving website search visibility based on official Google documentation

1862 votes

Tanstack Start

Build a full-stack TanStack Start app on Cloudflare Workers from scratch — SSR, file-based routing, server functions, D1+Drizzle, better-auth, Tailwind v4+shadcn/ui. Use whenever the user mentions TanStack Start, asks to scaffold a full-stack Cloudflare app with SSR, wants an SSR dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per ...

9881 votes

Pentest

PTES-aligned adversarial security audit for backend, frontend, and mobile applications. Produces a CVSS-scored Hacker Report with verified PoCs and phased remediation.

5491 votes
View all in development →