---
title: "3 Proven Practices: Lucide React Icons & shadcn/ui"
slug: "lucide-react-shadcn-ui-cta-best-practices"
published: "2025-11-19"
updated: "2025-11-12"
validated: "2025-11-12"
categories:
  - "React"
tags:
  - "Lucide React icons"
  - "shadcn/ui components"
  - "reusable CTA type"
  - "iconMap pattern"
  - "data-driven icons"
  - "Tailwind CSS customization"
  - "TypeScript types"
  - "CTAButton component"
  - "component library consistency"
  - "React UI best practices"
llm-intent: "reference"
audience-level: "intermediate"
framework-versions:
  - "lucide react"
  - "shadcn/ui"
  - "tailwind css"
  - "react"
  - "typescript"
status: "stable"
llm-purpose: "Lucide React icons: enforce consistent, data-driven icons, use shadcn/ui components, and reuse a CTA type to speed development—follow these practical…"
llm-prereqs:
  - "Access to Lucide React"
  - "Access to shadcn/ui"
  - "Access to Tailwind CSS"
  - "Access to React"
  - "Access to TypeScript"
llm-outputs:
  - "Completed outcome: Lucide React icons: enforce consistent, data-driven icons, use shadcn/ui components, and reuse a CTA type to speed development—follow these practical…"
---

**Summary Triples**
- (Icon storage, use-format, store icon as string name in data/types (e.g., 'Zap', 'Heart'))
- (Component resolution, implement, iconMap: Record<string, LucideIcon> mapping names to lucide-react components and render by lookup)
- (Fallback icon, provide, use a default icon (e.g., 'Star') when lookup fails to avoid runtime errors)
- (Import rule, enforce, always import lucide-react icons; do not inline or import raw SVGs directly)
- (CTA type reuse, centralize, define a single CTA TypeScript type and reuse it across components and CMS schemas)
- (Component composition, use, wrap shadcn/ui Button with a CTAButton that accepts CTA type and applies consistent styling)
- (Styling, control, use Tailwind utility classes centrally (e.g., in CTAButton) to ensure consistent sizes and colors)
- (Maintainability, benefit, data-driven icons + reusable CTA type enable global updates with minimal code changes)

### {GOAL}
Lucide React icons: enforce consistent, data-driven icons, use shadcn/ui components, and reuse a CTA type to speed development—follow these practical…

### {PREREQS}
- Access to Lucide React
- Access to shadcn/ui
- Access to Tailwind CSS
- Access to React
- Access to TypeScript

### {STEPS}
1. Adopt Lucide React for icons
2. Create an iconMap resolver
3. Use shadcn/ui for base components
4. Customize with Tailwind classes
5. Define a single CTA type
6. Implement a CTAButton component
7. Migrate components to the new patterns

<!-- llm:goal="Lucide React icons: enforce consistent, data-driven icons, use shadcn/ui components, and reuse a CTA type to speed development—follow these practical…" -->
<!-- llm:prereq="Access to Lucide React" -->
<!-- llm:prereq="Access to shadcn/ui" -->
<!-- llm:prereq="Access to Tailwind CSS" -->
<!-- llm:prereq="Access to React" -->
<!-- llm:prereq="Access to TypeScript" -->
<!-- llm:output="Completed outcome: Lucide React icons: enforce consistent, data-driven icons, use shadcn/ui components, and reuse a CTA type to speed development—follow these practical…" -->

# 3 Proven Practices: Lucide React Icons & shadcn/ui
> Lucide React icons: enforce consistent, data-driven icons, use shadcn/ui components, and reuse a CTA type to speed development—follow these practical…
Matija Žiberna · 2025-11-19

> Part 5 of the [Design to Code](/blog/design-driven-block-systems) series — Following [Creating Collections](/blog/creating-collections-reusable-data-entities)

Building consistent, maintainable code means leveraging existing tools effectively. This guide covers three critical practices: using Lucide React for icons, using shadcn/ui for components, and reusing the CTA type everywhere.

These practices prevent code duplication, ensure consistency, and make global changes trivial.

## Three Practices for Consistency

### Practice 1: Icons with Lucide React

Rule: Always use Lucide React. Never import SVGs directly.**

Lucide is already in your project. Every icon is consistent, customizable, and type-safe. Building a custom SVG system is wasted effort.

#### How to Use Lucide

In your types** (store icon as string name):

```typescript
export interface Feature {
  icon?: string;  // Icon name as string: 'Zap', 'Heart', 'Star'
}

export interface Industry {
  icon?: string;  // Same pattern everywhere
}
```

In your components** (resolve string to component):

```typescript
import { Zap, Heart, Star, ShoppingBag, LucideIcon } from 'lucide-react';

const iconMap: Record<string, LucideIcon> = {
  Zap,
  Heart,
  Star,
  ShoppingBag,
  // Add more as needed
};

export function MyComponent({ items }: Props) {
  return (
    <div>
      {items.map(item => {
        const Icon = iconMap[item.icon || 'Star'];  // Fallback to Star
        return (
          <div key={item.id}>
            {Icon && <Icon className="w-6 h-6 text-blue-600" />}
            <span>{item.title}</span>
          </div>
        );
      })}
    </div>
  );
}
```

In your data** (use icon name):

```typescript
const featureItem = {
  id: 1,
  title: 'Fast Setup',
  icon: 'Zap',  // ← String name, not component
};
```

#### Why This Approach

- **Consistent icons everywhere**: Same icon set, same styling
- **Data-driven**: Icons come from data, not hardcoded in components
- **Easy to change**: Update icon name in data, reflects everywhere
- **Type-safe**: TypeScript knows which icons exist
- **Customizable**: Size, color, stroke width via className

#### Common Lucide Icons

Popular icons you'll use:

- Arrows: `ArrowRight`, `ArrowLeft`, `ChevronDown`, `ChevronUp`
- Objects: `Heart`, `Star`, `Zap`, `Mail`, `Phone`, `MapPin`
- UI: `Menu`, `X`, `Search`, `Bell`, `Settings`, `User`
- Categories: `ShoppingBag`, `Briefcase`, `Heart`, `Lightbulb`, `Target`
- Status: `Check`, `CheckCircle`, `AlertCircle`, `Info`, `Download`

Full list: [lucide.dev](https://lucide.dev)

### Practice 2: Components with shadcn/ui

Rule: Use shadcn/ui for all generic UI. Never create custom Button, Card, Dialog, etc.**

shadcn/ui components are already in your project. They're accessible, consistent, and customizable. Building custom components duplicates work.

#### How to Use shadcn/ui

Import what you need:

```typescript
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog';
```

Use them as the base:

```typescript
export function MyComponent() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>My Section</CardTitle>
      </CardHeader>
      <CardContent>
        <p>Content here</p>
        <Button variant="primary">Click Me</Button>
      </CardContent>
    </Card>
  );
}
```

Customize with Tailwind:

```typescript
// If Figma shows a blue card with rounded corners and shadow:
<Card className="border-2 border-blue-600 rounded-lg shadow-lg p-6">
  <CardTitle className="text-xl text-blue-600">Title</CardTitle>
</Card>

// If you need custom spacing:
<Button
  className="px-8 py-4 text-lg font-bold rounded-xl"
  style={{
    backgroundColor: '#0066ff',
    borderRadius: '12px',
  }}
>
  Custom Button
</Button>
```

Never do this:

```typescript
// ❌ DON'T: Create custom button
function MyCustomButton() {
  return <button className="...">Click</button>;
}

// ❌ DON'T: Create custom card
function FeatureCard() {
  return <div className="...">Content</div>;
}

// ✅ DO: Use shadcn/ui and customize
<Button className="custom-classes">Click</Button>
<Card className="custom-classes">Content</Card>
```

#### Available shadcn/ui Components

Commonly used in blocks:

| Component | Use Case | Example |
|---|---|---|
| `Button` | All clickable actions | CTAs, form submission |
| `Card` | Container for content | Feature cards, testimonials |
| `Dialog` | Modal overlays | Forms, confirmations |
| `Input` | Text input fields | Search, forms |
| `Select` | Dropdown selection | Filters, options |
| `Carousel` | Image carousels | Project showcase |
| `Accordion` | Expandable sections | FAQs, features |
| `Tabs` | Tab panels | Different views |
| `Badge` | Labels/tags | Status, categories |

#### Customization Examples

Matching Figma Blue Gradient Button:**

Figma shows: Blue gradient background, white text, rounded 8px

```typescript
<Button
  className="bg-gradient-to-r from-blue-500 to-blue-700 text-white hover:from-blue-600 hover:to-blue-800 rounded-lg"
>
  Explore Features
</Button>
```

Matching Figma Minimal Card:**

Figma shows: Light border, no shadow, padding 24px

```typescript
<Card className="border border-gray-200 shadow-none p-6">
  <CardHeader className="pb-4">
    <CardTitle>Card Title</CardTitle>
  </CardHeader>
  <CardContent>Content</CardContent>
</Card>
```

### Practice 3: Reuse CTA Type Everywhere

The CTA (Call-To-Action) type is the most reusable type in your project. Use it for every button, link, and actionable element.

#### What is CTA?

```typescript
export interface CTA {
  label?: string;        // Button text
  href?: string;         // URL
  external?: boolean;    // Open in new tab
  variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
  icon?: string;         // Lucide icon name
  disabled?: boolean;
}
```

#### Where to Use CTA

In any block with buttons:

```typescript
export interface FeaturesBlock {
  items?: Feature[];
  cta?: CTA;  // Primary block CTA
}

export interface Feature {
  title: string;
  description: string;
  cta?: CTA;  // CTA on each item
}
```

In collections:

```typescript
export interface Industry {
  title: string;
  link?: CTA;  // Link to detail page
}

export interface TeamMember {
  name: string;
  socialLinks?: CTA[];  // Multiple links
}
```

In any component with interactive elements:

```typescript
export interface HeroSection {
  title: string;
  primaryCta?: CTA;
  secondaryCta?: CTA;
}
```

#### Creating a CTA Button Component

Create a reusable component that handles CTA rendering:

File: `src/components/cta-button.tsx`

```typescript
import type { CTA } from '@/types/blocks';
import { Button } from '@/components/ui/button';

interface CTAButtonProps {
  cta: CTA | undefined;
  className?: string;
}

export function CTAButton({ cta, className }: CTAButtonProps) {
  if (!cta) return null;

  return (
    <Button
      variant={cta.variant || 'primary'}
      disabled={cta.disabled}
      className={className}
      asChild
    >
      <a
        href={cta.href || '#'}
        target={cta.external ? '_blank' : '_self'}
        rel={cta.external ? 'noopener noreferrer' : undefined}
      >
        {cta.label || 'Learn More'}
      </a>
    </Button>
  );
}
```

Now use it everywhere:

```typescript
// In feature cards:
<CTAButton cta={feature.cta} />

// In hero section:
<CTAButton cta={data.primaryCta} className="text-lg" />

// In industry cards:
<CTAButton cta={industry.link} variant="outline" />
```

One component handles all CTA rendering consistently.

#### Benefit: One Change Fixes Everything

Update the CTA type or component once. Affects every button in your app.

```typescript
// Add tracking to CTA
export interface CTA {
  label?: string;
  href?: string;
  tracking?: {
    event: string;
    properties?: Record<string, string>;
  };
}

// CTAButton component handles tracking:
export function CTAButton({ cta }: CTAButtonProps) {
  const handleClick = () => {
    if (cta?.tracking) {
      analytics.track(cta.tracking.event, cta.tracking.properties);
    }
  };

  return (
    <Button onClick={handleClick}>
      <a href={cta?.href}>{cta?.label}</a>
    </Button>
  );
}

// Every button in the app now tracks! No changes needed to component files.
```

This is the power of reusable types.

## Putting It All Together

Here's how these three practices work in a real component:

```typescript
import { Heart, Star, Zap } from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { CTAButton } from '@/components/cta-button';
import type { Feature } from '@/types';

interface FeaturesGridProps {
  features: Feature[];
}

const iconMap = { Heart, Star, Zap };

export function FeaturesGrid({ features }: FeaturesGridProps) {
  return (
    <div className="grid grid-cols-3 gap-6">
      {features.map(feature => {
        const Icon = iconMap[feature.icon || 'Star'];
        return (
          <Card key={feature.id} className="hover:shadow-lg">
            <CardHeader>
              {Icon && <Icon className="w-8 h-8 text-blue-600 mb-4" />}
              <CardTitle>{feature.title}</CardTitle>
            </CardHeader>
            <CardContent>
              <p className="text-gray-600 mb-4">{feature.description}</p>
              <CTAButton cta={feature.cta} variant="outline" />
            </CardContent>
          </Card>
        );
      })}
    </div>
  );
}
```

This component:
- Uses Lucide icons (iconMap pattern)
- Uses shadcn/ui (Card, CardHeader, CardTitle)
- Reuses CTA type (CTAButton)
- Customizes with Tailwind (className)
- Data-driven (all content from props)
- Consistent across the app

## Consistency Rules

Follow these rules to maintain consistency across your entire application:

Icons:
- Store as string names in data
- Resolve in components using iconMap
- Use Lucide React always
- Never import custom SVGs
- Never hardcode icon components

Components:
- Use shadcn/ui for Button, Card, Dialog, Input, etc.
- Customize with Tailwind className
- Create custom components only for block-specific layouts
- Never rebuild Button, Card, or standard UI elements
- Never hardcode styles; use classes or type variants

CTAs:
- Use CTA type for every button/link
- Create CTAButton component once, use everywhere
- Update CTA type once, fixes everything
- Never create different button types for different sections
- Never hardcode link behavior

These simple rules prevent duplication, ensure consistency, and make maintaining your codebase trivial.

## LLM Response Snippet
```json
{
  "goal": "Lucide React icons: enforce consistent, data-driven icons, use shadcn/ui components, and reuse a CTA type to speed development—follow these practical…",
  "responses": [
    {
      "question": "What does the article \"3 Proven Practices: Lucide React Icons & shadcn/ui\" cover?",
      "answer": "Lucide React icons: enforce consistent, data-driven icons, use shadcn/ui components, and reuse a CTA type to speed development—follow these practical…"
    }
  ]
}
```