weird-ui
v0.1.4
Published
A shadcn/ui-style component library. Copy components into your project, not dependencies.
Readme
weird-ui
A shadcn/ui-style component library for React. Components are copied into your project, not imported as dependencies. This means:
✅ Tailwind styles always work - No node_modules scanning issues
✅ Full ownership - Modify components to match your design
✅ Zero runtime dependencies - Components are just files
✅ Type-safe - Full TypeScript support
✅ Pre-built sections - Ready-to-use page sections
This is NOT a npm package to install. It's a component scaffold you copy into your project.
Quick Start
1. Initialize weird-ui in your project
npx weird-ui initThis will:
- ✓ Install Tailwind CSS (if not present)
- ✓ Create
src/lib/utils.tswith thecn()utility - ✓ Install
clsxandtailwind-mergedependencies
2. Add components
npx weird-ui add button
npx weird-ui add card
npx weird-ui add inputComponents are copied to src/components/ui/.
3. Start using
import { Button } from "@/components/ui/button"
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"
export default function App() {
return (
<Card>
<CardHeader>
<CardTitle>Welcome</CardTitle>
</CardHeader>
<CardContent>
<Button>Click me</Button>
</CardContent>
</Card>
)
}Components Overview
Atomic UI Components
Simple, reusable building blocks for your UI.
| Component | Command | Description |
|-----------|---------|-------------|
| Button | npx weird-ui add button | Flexible button with variants |
| Card | npx weird-ui add card | Container with sub-components (Header, Content, Footer) |
| Input | npx weird-ui add input | Form input with error states |
| Badge | npx weird-ui add badge | Label component with variants |
| Textarea | npx weird-ui add textarea | Multi-line text input with error states |
| Accordion | npx weird-ui add accordion | Expandable/collapsible content |
| Tabs | npx weird-ui add tabs | Tabbed interface for organizing content |
| Modal | npx weird-ui add modal | Dialog/modal component with backdrop |
Section Components
Full-featured, drop-in page sections that combine multiple UI elements.
| Component | Command | Description |
|-----------|---------|-------------|
| Hero Section | npx weird-ui add hero-section | Full-width hero with title, subtitle, and CTAs |
| FAQ Section | npx weird-ui add faq-section | Interactive FAQ with expandable items |
| Gallery Section | npx weird-ui add gallery-section | Responsive image gallery |
| Feature Section | npx weird-ui add feature-section | Features showcase with icons |
| CTA Section | npx weird-ui add cta-section | Call-to-action banner |
Component Structure
After running npx weird-ui add button, you get:
src/
├── components/
│ ├── ui/
│ │ ├── button.tsx ← Your copy
│ │ ├── card.tsx
│ │ ├── input.tsx
│ │ ├── badge.tsx
│ │ ├── textarea.tsx
│ │ ├── accordion.tsx
│ │ ├── tabs.tsx
│ │ └── modal.tsx
│ └── sections/ ← Page-level components
│ ├── hero-section.tsx
│ ├── faq-section.tsx
│ ├── gallery-section.tsx
│ ├── feature-section.tsx
│ └── cta-section.tsx
├── lib/
│ └── utils.ts ← cn() utility
└── tailwind.config.jsHow It Works
weird-ui ships source files, not compiled code. The CLI copies .tsx files into your project where:
- You own the source code
- Tailwind CSS scans your local
src/directory - No build-time or runtime magic
- Full control over styling and behavior
Atomic UI Components
Button
Flexible button with multiple variants.
import { Button } from "@/components/ui/button"
export default function App() {
return (
<>
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button disabled>Disabled</Button>
</>
)
}Card
Container component with sub-components.
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
CardFooter,
} from "@/components/ui/card"
export default function App() {
return (
<Card>
<CardHeader>
<CardTitle>Card Title</CardTitle>
<CardDescription>Card description goes here</CardDescription>
</CardHeader>
<CardContent>Content here</CardContent>
<CardFooter>Footer content</CardFooter>
</Card>
)
}Input
Form input with error states.
import { Input } from "@/components/ui/input"
import { useState } from "react"
export default function App() {
const [value, setValue] = useState("")
const [error, setError] = useState(false)
return (
<Input
placeholder="Enter email"
value={value}
onChange={(e) => setValue(e.target.value)}
error={error}
helperText={error ? "Invalid email" : ""}
/>
)
}Badge
Label component with multiple variants.
import { Badge } from "@/components/ui/badge"
export default function App() {
return (
<>
<Badge variant="default">Default</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="destructive">Error</Badge>
<Badge variant="outline">Outline</Badge>
</>
)
}Textarea
Multi-line text input.
import { Textarea } from "@/components/ui/textarea"
export default function App() {
return (
<Textarea
placeholder="Enter message"
helperText="Max 500 characters"
/>
)
}Accordion
Expandable/collapsible content.
import { Accordion } from "@/components/ui/accordion"
export default function App() {
const items = [
{ id: "1", title: "Item 1", content: "Content 1" },
{ id: "2", title: "Item 2", content: "Content 2" },
]
return <Accordion items={items} allowMultiple={false} />
}Tabs
Tabbed interface.
import { Tabs } from "@/components/ui/tabs"
export default function App() {
const tabs = [
{ id: "tab1", label: "Tab 1", content: "Content 1" },
{ id: "tab2", label: "Tab 2", content: "Content 2" },
]
return <Tabs tabs={tabs} defaultTabId="tab1" />
}Modal
Dialog/modal component.
import { Modal } from "@/components/ui/modal"
import { Button } from "@/components/ui/button"
import { useState } from "react"
export default function App() {
const [open, setOpen] = useState(false)
return (
<>
<Button onClick={() => setOpen(true)}>Open Modal</Button>
<Modal
open={open}
onOpenChange={setOpen}
title="Modal Title"
footer={
<>
<Button onClick={() => setOpen(false)}>Cancel</Button>
<Button variant="primary">Confirm</Button>
</>
}
>
<p>Modal content goes here</p>
</Modal>
</>
)
}Section Components
Hero Section
Full-width hero banner.
import { HeroSection } from "@/components/sections/hero-section"
export default function App() {
return (
<HeroSection
title="Build Something Amazing"
subtitle="Create beautiful interfaces with weird-ui"
primaryCTA={{
label: "Get Started",
onClick: () => console.log("clicked"),
}}
secondaryCTA={{
label: "Learn More",
onClick: () => console.log("learn more"),
}}
backgroundImage="https://example.com/image.jpg"
/>
)
}FAQ Section
Interactive FAQ with expand/collapse.
import { FAQSection } from "@/components/sections/faq-section"
export default function App() {
const faqs = [
{ question: "How does it work?", answer: "Components are copied to your project..." },
{ question: "Can I modify components?", answer: "Yes, you own the source code!" },
{ question: "Do I need to install weird-ui?", answer: "No, it's a scaffolding tool." },
]
return (
<FAQSection
title="Frequently Asked Questions"
subtitle="Get answers to common questions"
items={faqs}
/>
)
}Gallery Section
Responsive image gallery.
import { GallerySection } from "@/components/sections/gallery-section"
export default function App() {
const images = [
{ src: "image1.jpg", alt: "Image 1", caption: "Caption 1" },
{ src: "image2.jpg", alt: "Image 2", caption: "Caption 2" },
{ src: "image3.jpg", alt: "Image 3", caption: "Caption 3" },
]
return (
<GallerySection
title="Our Gallery"
subtitle="Beautiful images"
images={images}
columns={3}
/>
)
}Feature Section
Features showcase.
import { FeatureSection } from "@/components/sections/feature-section"
export default function App() {
const features = [
{
title: "Feature 1",
description: "Description of feature 1",
icon: "🚀",
},
{
title: "Feature 2",
description: "Description of feature 2",
icon: "⚡",
},
{
title: "Feature 3",
description: "Description of feature 3",
icon: "✨",
},
]
return (
<FeatureSection
title="Why Choose Us"
subtitle="Here's what makes us different"
features={features}
columns={3}
/>
)
}CTA Section
Call-to-action banner.
import { CTASection } from "@/components/sections/cta-section"
export default function App() {
return (
<CTASection
headline="Ready to get started?"
description="Join thousands of developers using weird-ui"
buttonLabel="Start Now"
onClick={() => console.log("clicked")}
variant="default"
/>
)
}The cn() Utility
All components use the cn() utility to merge Tailwind classes safely:
import { cn } from "@/lib/utils"
cn(
"px-4 py-2",
active && "bg-blue-500",
disabled && "opacity-50"
)
// → "px-4 py-2 bg-blue-500 opacity-50"The cn() function uses clsx and tailwind-merge to:
- Combine multiple classes
- Remove conflicting Tailwind utilities
- Support conditional classes
TypeScript Support
All components are fully typed. TypeScript support is built-in across all atomic UI and section components.
Customizing Components
After copying, edit components directly in your project:
// src/components/ui/button.tsx
export const Button = ({ className, variant = "primary", ...props }: ButtonProps) => {
return (
<button
className={cn(
// Modify Tailwind classes here
"px-4 py-3 rounded-lg font-bold",
// ...
)}
{...props}
/>
)
}Tailwind CSS Configuration
Make sure your tailwind.config.js includes the content paths:
module.exports = {
content: [
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}CLI Commands
# Initialize project with utils and Tailwind
npx weird-ui init
# Add UI components
npx weird-ui add button
npx weird-ui add card
npx weird-ui add input
npx weird-ui add badge
npx weird-ui add textarea
npx weird-ui add accordion
npx weird-ui add tabs
npx weird-ui add modal
# Add section components
npx weird-ui add hero-section
npx weird-ui add faq-section
npx weird-ui add gallery-section
npx weird-ui add feature-section
npx weird-ui add cta-section
# Overwrite existing component
npx weird-ui add button --overwriteFAQ
Q: Do I need to install weird-ui as a dependency?
A: No. You only use npx weird-ui to scaffold components. The package itself is never imported.
Q: What if Tailwind styles aren't working?
A: Make sure your tailwind.config.js includes:
content: [
"./src/**/*.{js,ts,jsx,tsx}",
]Q: Can I modify copied components?
A: Yes! That's the whole point. Edit src/components/ui/*.tsx directly.
Q: How do I update components?
A: Run npx weird-ui add <component> --overwrite to replace your copy.
Q: What's the difference between UI and Section components?
A: UI components are atomic building blocks (Button, Input, Badge, etc.). Section components are pre-built page sections that combine multiple UI elements and often include state management (Hero, FAQ, Gallery, Feature, CTA).
Q: Can I use sections without the UI components?
A: Yes! Section components are self-contained. You can add just the sections you need.
Q: Are there runtime dependencies?
A: Components only use clsx and tailwind-merge (installed by npx weird-ui init). No other dependencies are required.
Q: Can I customize section components?
A: Absolutely! After copying, edit sections directly. Add your own styles, change layouts, or add new props.
License
MIT
Contributing
Found a bug? Have an idea? Open an issue or submit a PR!
weird-ui - Copy components, own the code.
