Agent Skills: Lucide Icons in Rails

Expert guidance for using Lucide icons in Rails applications via the lucide-rails gem. Use when adding icons, choosing icon names, styling SVG icons, making icons accessible, or building icon-based UI patterns (buttons, nav, badges, empty states). Triggers on "lucide", "icon", "lucide-rails", "SVG icon", "icon library", "lucide_icon", "feather icon", "inline SVG", "add an icon", "icon button", "icon name".

UncategorizedID: thinkoodle/rails-skills/lucide-icons

Install this agent skill to your local

pnpm dlx add-skill https://github.com/ThinkOodle/rails-skills/tree/HEAD/skills/lucide-icons

Skill Files

Browse the full folder contents for lucide-icons.

Download Skill

Loading file tree…

skills/lucide-icons/SKILL.md

Skill Metadata

Name
lucide-icons
Description
Expert guidance for using Lucide icons in Rails applications via the lucide-rails gem. Use when adding icons, choosing icon names, styling SVG icons, making icons accessible, or building icon-based UI patterns (buttons, nav, badges, empty states). Triggers on "lucide", "icon", "lucide-rails", "SVG icon", "icon library", "lucide_icon", "feather icon", "inline SVG", "add an icon", "icon button", "icon name".

Lucide Icons in Rails

Render Lucide icons as inline SVGs via the lucide-rails gem. Icons use currentColor so they inherit text color automatically.

When To Use This Skill

  • Adding icons to a Rails view (buttons, nav, badges, empty states)
  • Choosing the right Lucide icon name for a UI element
  • Making icons accessible (decorative vs interactive)
  • Styling or sizing SVG icons with CSS

Philosophy

  1. Icons are decoration until they aren't — Decorative icons need no label. Interactive icons (buttons, links) MUST have accessible text.
  2. Size intentionally — Always pass size:. The default 24px is too large for most inline contexts.
  3. Let CSS handle color — Icons inherit currentColor. Style the parent, not the SVG.
  4. Use the helper, never raw SVGslucide_icon keeps icons consistent, cacheable, and updatable.
  5. kebab-case names only — It's "chevron-right", never "chevronRight" or "ChevronRight".

Installation

# Gemfile
gem "lucide-rails"
bundle install

No JavaScript, no import maps, no asset pipeline config. The gem provides a Ruby helper that renders inline SVGs.

Basic Usage

<%= lucide_icon "home", size: 18 %>

Renders:

<svg width="18" height="18" viewBox="0 0 24 24" fill="none"
     stroke="currentColor" stroke-width="2"
     stroke-linecap="round" stroke-linejoin="round">
  <path d="..."></path>
</svg>

Helper Parameters

| Parameter | Default | Description | |-----------|---------|-------------| | size | 24 | Width and height in pixels | | class | nil | CSS classes | | stroke_width | 2 | Stroke width (1–3 typical range) |

<%= lucide_icon "home", size: 16, class: "text-muted" %>
<%= lucide_icon "home", size: 24, stroke_width: 1.5 %>

Any additional HTML attributes are passed through to the <svg> element:

<%= lucide_icon "copy", size: 14, data: { action: "click->clipboard#copy" } %>
<%= lucide_icon "x", size: 18, aria: { label: "Close" } %>

Size Guidelines

Pick size based on context. Be consistent within each context.

| Context | Size | When | |---------|------|------| | Badges, inline labels | 12px | Tight spaces, small text | | Dismiss/close, inline actions | 14px | Body text companion | | Buttons with text | 16px | Most common for actions | | Navigation sidebar | 18px | Nav links, menu items | | Card/list item icons | 20px | Content type indicators | | Empty states, hero | 32–48px | Large decorative icons |

Styling with CSS

Color Inheritance

Icons stroke with currentColor — they inherit the parent's color:

.nav-link { color: var(--color-muted); }
.nav-link:hover { color: var(--color-default); }
/* Icon color changes automatically on hover */

Direct Icon Styling

When you need icon-specific colors, use a utility class on the parent or icon wrapper:

.text-success { color: var(--color-positive); }
.text-danger  { color: var(--color-negative); }
.text-warning { color: var(--color-warning); }
.text-muted   { color: var(--color-muted); }
<span class="text-success"><%= lucide_icon "check", size: 14 %></span>
<span class="text-danger"><%= lucide_icon "alert-circle", size: 14 %></span>

Stroke Width

Thinner strokes look more refined at larger sizes. Thicker strokes read better small.

<%= lucide_icon "settings", size: 48, stroke_width: 1.5 %>  <%# Large, elegant %>
<%= lucide_icon "check", size: 12, stroke_width: 2.5 %>     <%# Small, readable %>

Dark Mode

Since icons use currentColor, they adapt to dark mode when your text colors do. No extra work needed if your color system uses CSS custom properties.

Common Patterns

Icon + Text Button

<button class="btn btn-primary">
  <%= lucide_icon "plus", size: 16 %>
  Create Project
</button>
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

Icon-Only Button

<button class="btn-icon" aria-label="Close">
  <%= lucide_icon "x", size: 18 %>
</button>
.btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.5rem;
}

Icon-only buttons MUST have aria-label.

Navigation Link

<%= link_to dashboard_path, class: "nav-link" do %>
  <%= lucide_icon "layout-dashboard", size: 18 %>
  Dashboard
<% end %>

Badge with Icon

<span class="badge">
  <%= lucide_icon "lock", size: 12 %>
  Restricted
</span>
.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
}

Empty State

<div class="empty-state">
  <%= lucide_icon "folder-plus", size: 48, stroke_width: 1.5 %>
  <h3>No projects yet</h3>
  <p>Create your first project to get started.</p>
</div>
.empty-state {
  text-align: center;
  padding: 3rem 1rem;
}
.empty-state svg {
  color: var(--color-muted);
  margin-bottom: 1rem;
}

Icon in Link (Turbo)

<%= link_to edit_post_path(@post), class: "action-link", data: { turbo_frame: "post_form" } do %>
  <%= lucide_icon "edit", size: 14 %>
  Edit
<% end %>

Stimulus Integration

<button data-controller="clipboard" data-action="click->clipboard#copy"
        data-clipboard-text-value="<%= @invite_url %>">
  <%= lucide_icon "copy", size: 14 %>
  Copy Link
</button>

Accessibility

Rule: Decorative vs. Meaningful

Decorative — icon accompanies visible text. No extra markup needed:

<button>
  <%= lucide_icon "plus", size: 16 %> Add Item
</button>

Meaningful — icon is the only content. MUST add accessible text:

<%# Option 1: aria-label on the interactive element %>
<button aria-label="Close" class="btn-icon">
  <%= lucide_icon "x", size: 18 %>
</button>

<%# Option 2: sr-only span %>
<button class="btn-icon">
  <%= lucide_icon "x", size: 18 %>
  <span class="sr-only">Close</span>
</button>

Screen Reader Only Class

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

SVGs Are aria-hidden by Default

The lucide_icon helper renders SVGs that screen readers skip. This is correct — the accessible name belongs on the interactive parent element, not the icon.

Common Icons by Use Case

Navigation

| Icon Name | Use | |-----------|-----| | home | Home/Dashboard | | layout-dashboard | Dashboard/Overview | | settings | Settings | | users | Team/People | | file-text | Documents/Pages | | log-out | Sign out | | log-in | Sign in | | menu | Hamburger menu | | search | Search |

Actions

| Icon Name | Use | |-----------|-----| | plus | Create/Add | | edit | Edit/Modify | | trash | Delete | | copy | Copy to clipboard | | x | Close/Dismiss | | check | Confirm/Save | | eye | View/Preview | | eye-off | Hide | | download | Download | | upload | Upload | | external-link | Open in new tab |

Arrows & Chevrons

| Icon Name | Use | |-----------|-----| | arrow-left | Back | | arrow-right | Forward | | chevron-down | Expand/Dropdown | | chevron-right | Next/Drill-in | | chevron-up | Collapse | | arrow-up-right | External/Outbound |

Status & Feedback

| Icon Name | Use | |-----------|-----| | check-circle | Success | | alert-circle | Error | | alert-triangle | Warning | | info | Information | | help-circle | Help | | loader | Loading (animate with CSS) | | lock | Restricted/Private | | unlock | Public/Open |

Communication

| Icon Name | Use | |-----------|-----| | mail | Email | | bell | Notifications | | message-circle | Chat/Comments | | megaphone | Announcements | | send | Send message |

Files & Content

| Icon Name | Use | |-----------|-----| | file | Generic file | | file-text | Document | | folder | Folder/Category | | folder-plus | New folder | | image | Image/Photo | | video | Video | | link | Link/URL |

Social & Misc

| Icon Name | Use | |-----------|-----| | github | GitHub | | twitter | Twitter/X | | globe | Website/World | | calendar | Date/Schedule | | clock | Time/History | | star | Favorite | | heart | Like | | filter | Filter | | sort-asc | Sort ascending | | sort-desc | Sort descending |

Anti-Patterns

  1. Wrong icon name casing"chevronRight" → use "chevron-right" (kebab-case always)
  2. Missing size — Don't rely on default 24px. Be explicit: size: 16
  3. Raw <svg> or <img> tags — Always use lucide_icon helper
  4. Styling SVG directly — Style the parent element's color, not the SVG's stroke
  5. Icon-only button without aria-label — Screen readers can't describe it
  6. Inconsistent sizes — Pick a size per context and stick to it
  7. Too many different icons — Reuse the same icon for the same concept across your app
  8. Using icons without visible text when meaning is ambiguous — A pencil icon might mean "edit" or "write." Add text or a tooltip.

Finding Icons

Browse the full library at lucide.dev/icons.

Search tips:

  • Search by concept: "arrow", "file", "user", "check"
  • Icons are named descriptively: circle-check not checkmark-in-circle
  • Related icons share prefixes: file, file-text, file-plus, file-x
  • If an icon doesn't exist in lucide-rails, the helper will raise an error at render time

Reference

For detailed patterns, edge cases, and advanced usage, see reference.md.