Setting Up A Shared UI Component Library With Shadcn/UI And Viable Brand Colors
In modern web development, component libraries are essential for creating consistent, scalable, and maintainable user interfaces. Shadcn/UI has emerged as a popular choice for React developers, offering a collection of unstyled components that can be easily customized with Tailwind CSS. This article will guide you through setting up a shared UI component library using shadcn/ui within a monorepo structure, focusing on creating a cohesive design system with custom brand colors and theming.
Target Directory: /packages/ui/
We'll be working within the /packages/ui/
directory, which is a common setup for monorepos where UI components are isolated into their own package. This approach promotes modularity and allows for easier sharing and reuse of components across different projects within the monorepo.
Task Objective: Creating a Shared UI Component Library with Shadcn/UI and Viable Brand Colors
The primary objective is to establish a shared UI component library utilizing shadcn/ui, incorporating Viable's specific brand colors. This library will serve as a centralized repository for reusable UI elements, ensuring consistency and efficiency in our development workflow. By leveraging shadcn/ui's unstyled components and Tailwind CSS, we'll have the flexibility to create a unique and cohesive design system.
Key Steps in Achieving the Objective
- Initialize shadcn/ui with Next.js compatibility.
- Configure brand colors (primary: #33B6CC, accent: #F4B6C2, neutral: #4D4D4D).
- Create base components: Button, Card, Input, Select, Dialog.
- Set up Tailwind CSS with a custom theme.
- Add framer-motion animations for enhanced user experience.
- Export all components with proper TypeScript types for type safety and developer experience.
Acceptance Criteria: Ensuring Quality and Functionality
To ensure the UI component library meets our standards, we've established specific acceptance criteria:
- Initialization of shadcn/ui with Next.js compatibility: The library must be correctly initialized to work seamlessly within a Next.js environment.
- Configuration of brand colors: The primary (#33B6CC), accent (#F4B6C2), and neutral (#4D4D4D) brand colors must be accurately integrated into the component library's theme.
- Creation of base components: Essential components such as Button, Card, Input, Select, and Dialog must be implemented and styled.
- Tailwind CSS setup with a custom theme: Tailwind CSS should be configured with a custom theme that includes our brand colors and other design tokens.
- Framer-motion animations: Animations using framer-motion should be added to enhance the user experience and provide visual feedback.
- Export of all components with proper TypeScript types: All components must be exported with appropriate TypeScript types to ensure type safety and improve developer experience.
Technical Requirements: Tools and Technologies
The UI component library will be built using the following technical stack:
- shadcn/ui (latest version): We'll use the latest version of shadcn/ui to leverage its features and improvements.
- Tailwind CSS v3: Tailwind CSS v3 will be used for styling the components, providing a utility-first approach.
- Dark mode support: The library must support both light and dark modes, adapting to user preferences.
- Full TypeScript support: TypeScript will be used throughout the library to ensure type safety and improve code maintainability.
Claude Instructions: Guiding the Implementation
To guide the implementation process, we'll provide specific instructions to Claude, our AI assistant:
- Work ONLY within
/packages/ui/
to maintain the monorepo structure. - Create a proper
package.json
with the name@viable/ui
to define the package and its dependencies. - Ensure all components are accessible and follow WCAG guidelines to provide an inclusive user experience.
Step-by-Step Implementation
Let's dive into the step-by-step implementation of our shared UI component library.
1. Setting Up the Project
First, navigate to the /packages/ui/
directory in your terminal. If you don't have a package.json
file yet, create one by running:
npm init -y
Next, update the package.json
file to include the name @viable/ui
and other relevant information:
{
"name": "@viable/ui",
"version": "0.1.0",
"description": "Shared UI component library for Viable projects",
"main": "index.js",
"license": "MIT",
"dependencies": {
// Dependencies will be added here
},
"devDependencies": {
// Dev dependencies will be added here
}
}
2. Installing Dependencies
Install the necessary dependencies, including shadcn/ui, Tailwind CSS, and other required packages:
npm install shadcn-ui@latest tailwindcss@latest postcss autoprefixer class-variance-authority clsx tailwind-merge lucide-react framer-motion react react-dom
Also, install the necessary dev dependencies:
npm install -D typescript eslint prettier eslint-config-prettier eslint-plugin-tailwindcss postcss-cli concurrently
3. Initializing Shadcn/UI
Initialize shadcn/ui within your project. This command will set up the necessary configuration files and dependencies:
npx shadcn-ui init -c components.json
This command creates a components.json
file in your project's root directory, which manages the configuration for shadcn/ui components.
4. Configuring Tailwind CSS
Generate the tailwind.config.js
and postcss.config.js
files if they don't exist:
npx tailwindcss init -p
Open tailwind.config.js
and configure the theme to include Viable's brand colors:
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ['class'],
content: [
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
],
prefix: '',
theme: {
container: {
center: true,
padding: '2rem',
screens: {
'2xl': '1400px',
},
},
extend: {
colors: {
primary: {
DEFAULT: '#33B6CC',
foreground: '#000000', // You might want to adjust this
},
accent: {
DEFAULT: '#F4B6C2',
foreground: '#000000', // You might want to adjust this
},
neutral: {
DEFAULT: '#4D4D4D',
foreground: '#FFFFFF', // You might want to adjust this
},
// ... other colors
},
borderRadius: {
lg: `var(--radius)`,
md: `calc(var(--radius) - 2px)`,
sm: 'calc(var(--radius) - 4px)',
},
keyframes: {
'accordion-down': {
from: { height: '0' },
to: { height: 'var(--radix-accordion-content-height)' },
},
'accordion-up': {
from: { height: 'var(--radix-accordion-content-height)' },
to: { height: '0' },
},
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
},
},
},
plugins: [require('tailwindcss-animate')],
}
This configuration defines the primary, accent, and neutral colors according to the specifications. It also includes configurations for dark mode, border radius, keyframes, and animations.
5. Setting Up Global Styles
Create a globals.css
file (or modify your existing global styles file) and add the Tailwind CSS directives:
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 47.4% 11.2%;
--card: 0 0% 100%;
--card-foreground: 222.2 47.4% 11.2%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--primary: 171 58% 50%;
--primary-foreground: 355 100% 10%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--accent: 348 66% 78%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 20% 98%;
--ring: 215 20.2% 65.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 20% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 16.6% 33.1%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 20% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 20% 98%;
--border: 216 34% 17%;
--input: 216 34% 17%;
--primary: 171 58% 50%;
--primary-foreground: 355 100% 10%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 20% 98%;
--accent: 348 66% 78%;
--accent-foreground: 210 20% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 20% 98%;
--ring: 216 34% 17%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
This file sets up the base styles and color variables for your application, including dark mode support.
6. Creating Base Components
Now, let's create the base components: Button, Card, Input, Select, and Dialog. You can use the shadcn-ui add
command to add these components:
npx shadcn-ui add button
npx shadcn-ui add card
npx shadcn-ui add input
npx shadcn-ui add select
npx shadcn-ui add dialog
This command will add the corresponding component files to your components/ui
directory.
Customizing the Button Component
Open components/ui/button.tsx
and customize the styles to match Viable's brand colors. For example:
import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const buttonVariants = cva(
'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive:
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline:
'bg-transparent border border-input hover:bg-accent hover:text-accent-foreground',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'underline-offset-4 hover:underline',
},
size: {
default: 'h-10 px-4 py-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<
HTMLButtonElement,
ButtonProps
>(({ className, children, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button'
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
>
{children}
</Comp>
)
})
Button.displayName = 'Button'
export { Button, buttonVariants
}
In this example, the default variant of the Button component uses the bg-primary
and text-primary-foreground
classes, which correspond to Viable's primary brand color.
Customizing Other Components
Similarly, customize the other base components (Card, Input, Select, Dialog) to incorporate Viable's brand colors and styles. Pay attention to the different states (hover, focus, disabled) and ensure they align with the design system.
7. Adding Framer-Motion Animations
To add animations, you can use framer-motion. For example, to add a simple fade-in animation to the Card component, you can modify components/ui/card.tsx
:
import * as React from 'react'
import * as CardPrimitive from '@radix-ui/react-card'
import { motion } from 'framer-motion'
import { cn } from '@/lib/utils'
const Card = motion(CardPrimitive.Root)
const CardHeader = CardPrimitive.Header
const CardFooter = CardPrimitive.Footer
const CardTitle = CardPrimitive.Title
const CardDescription = CardPrimitive.Description
const CardContent = CardPrimitive.Content
const MotionCard = React.forwardRef<React.ElementRef<typeof Card>,
React.ComponentPropsWithoutRef<typeof Card>>(({ className, ...props }, ref) => (
<Card
{...props}
className={cn('rounded-lg border bg-card text-card-foreground shadow-sm', className)}
ref={ref}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
/>
))
MotionCard.displayName = CardPrimitive.Root.displayName
export {MotionCard as Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent}
This adds a fade-in animation when the Card component mounts. You can add similar animations to other components to enhance the user experience.
8. Exporting Components with TypeScript Types
Ensure all components are exported with proper TypeScript types. This helps in maintaining type safety and improves the developer experience. Create an index.ts
file in the components/ui
directory to export all components:
export * from './button'
export * from './card'
export * from './input'
export * from './select'
export * from './dialog'
// Export other components
9. Ensuring Accessibility (WCAG Guidelines)
Accessibility is crucial for creating inclusive user interfaces. Ensure all components follow WCAG guidelines. This includes providing proper ARIA attributes, ensuring sufficient color contrast, and making components keyboard-navigable.
For example, when creating the Button component, ensure it has the correct ARIA attributes and that the text has sufficient contrast with the background color.
10. Testing and Documentation
Thoroughly test your components to ensure they function correctly and meet the acceptance criteria. Write documentation for each component, including usage examples and API references. This will help other developers in your team use the library effectively.
Conclusion: Building a Robust UI Component Library
Setting up a shared UI component library with shadcn/ui is a crucial step in creating a consistent and maintainable design system. By following the steps outlined in this article, you can create a robust library that incorporates your brand colors, supports dark mode, and provides a seamless developer experience. Remember to prioritize accessibility and documentation to ensure the library is usable and valuable to your entire team. This approach will save development time, reduce inconsistencies, and ultimately improve the quality of your applications.
By focusing on reusability, theming, and accessibility, your UI component library will become a cornerstone of your development process, enabling you to build high-quality user interfaces efficiently and effectively. Leveraging shadcn/ui's unstyled components and Tailwind CSS gives you the flexibility to create a unique and cohesive design system tailored to your specific needs.