Link

A link component that renders <a> elements with conditional rendering support and full Turbo integration.

Key Features

  • Conditional Rendering, Support for if:, unless:, and unless_current:
  • Turbo Integration, Full support for Turbo Frame, Stream, Method, and Prefetch
  • External Links, Automatic security attributes for external URLs
  • Icon Support, Leading and trailing icons
  • Underline Variants, Four different underline styles
  • Active State, Highlight navigation/tabs with active styling
  • Text Truncation, Auto-truncate long text with tooltips
  • SEO Features, Enhanced rel attributes (sponsored, ugc, nofollow)
  • Communication Links, Shortcuts for email, phone, and SMS links
  • Accessibility, HrefLang, tooltips, and referrer policy support

Basic Usage

The link component supports both positional and keyword argument styles. By default, links are blue and show an underline on hover.

Basic Usage
# Simple link with defaults (blue, hover underline)
<%= rui_link("View Posts", "/posts") %>

# Positional (recommended) 
<%= rui_link("View Posts", "#", color: :primary) %>

# With block 
<%= rui_link("/posts", color: :primary) do %>
  View Posts
<% end %>

# Keyword (backward compatible) 
<%= rui_link(url: "/posts", text: "View Posts", color: :primary) %>

Variants

Four distinct underline styles. The default is hover_underline, which shows an underline on hover for clear visual feedback.

Link Variants
<%= rui_link("Underline", "#", variant: :underline) %>
<%= rui_link("No Underline", "#", variant: :no_underline) %>
<%= rui_link("Hover Underline", "#") %> 
<%= rui_link("Animated Underline", "#", variant: :animated_underline) %>

Navigation Variant (:nav)

The :nav variant is designed for sidebar and navigation menus. It features:

  • Auto-detect active state - Automatically highlights the current page
  • Block-level styling - Full-width with padding and rounded corners
  • Hover backgrounds - Subtle hover effect for better UX
  • Color theming - Supports all colors for themed sections

💡 Pro Tip: The :nav variant defaults to color: :neutral for standard navigation. The active state is automatically detected using Rails' current_page? helper.

Navigation - Auto-Detect Active Page
<nav class="space-y-1">
  <%= rui_link("Link", "/docs/link", variant: :nav) %>
  <%= rui_link("Button", "/docs/button", variant: :nav) %>
  <%= rui_link("Icon", "/docs/icon", variant: :nav) %>
</nav>

Colored Navigation Sections

Use colors to create themed navigation sections:

Default (Neutral)

Blue Section

Green Section

Colored Navigation Sections
<nav class="space-y-1">
  <%= rui_link("Analytics", "/analytics", variant: :nav, color: :blue) %>
  <%= rui_link("Reports", "/reports", variant: :nav, color: :blue) %>
</nav>

<%= rui_link("Billing", "/billing", variant: :nav, color: :green, active: true) %>

Navigation with Icons

Combine :nav variant with icons for rich sidebar navigation:

Navigation with Icons
<nav class="space-y-1">
  <%= rui_link("Dashboard", "/dashboard", variant: :nav) do |link| %>
    <% link.with_icon(:layout_dashboard) %>
  <% end %>
  <%= rui_link("Projects", "/projects", variant: :nav) do |link| %>
    <% link.with_icon(:folder) %>
  <% end %>
  <%= rui_link("Team", "/team", variant: :nav) do |link| %>
    <% link.with_icon(:users) %>
  <% end %>
</nav>

Colors

All semantic and Tailwind colors are supported via ColorBuilderHelper. The default is blue.

Semantic Colors

Configurable semantic colors that map to Tailwind colors:

Tailwind Colors

All Tailwind default colors are supported:

Link Colors
# Semantic colors 
<%= rui_link("Primary", "#", color: :primary) %>
<%= rui_link("Success", "#", color: :success) %>
<%= rui_link("Danger", "#", color: :danger) %>

# Tailwind colors 
<%= rui_link("Purple", "#", color: :purple) %>
<%= rui_link("Teal", "#", color: :teal) %>
<%= rui_link("Rose", "#", color: :rose) %>

Sizes

Five size options from xs to xl.

Link Sizes
<%= rui_link("Small Link", "#", size: :sm) %>
<%= rui_link("Base Link", "#", size: :base) %>
<%= rui_link("Large Link", "#", size: :lg) %>

Conditional Rendering

Links can conditionally render as text based on conditions.

Conditional Rendering
<%= rui_link("Admin Panel", "/admin", if: admin?, color: :primary) %%>

<%= rui_link("Login", "/login", unless: user_signed_in?) %%>

<%= rui_link("Posts", posts_path, unless_current: true) %%>
if: trueAdmin Panel
if: falseAdmin Panel
unless: falsePosts
unless: truePosts

States

Disabled and loading states are supported.

Link States
<%= rui_link("Disabled", "#", disabled: true) %%>
<%= rui_link("Loading", "#", loading: true) %%>

External Links & Security

External links automatically get security attributes to protect your users and prevent common vulnerabilities.

🔒 Security by Default

Per https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/a#security_and_privacy , external links automatically include:

  • rel="noopener" - Prevents window.opener exploitation (tabnabbing attacks)
  • rel="noreferrer" - Blocks referrer information from being sent
  • target="_blank" - Opens in new tab for better UX

You don't need to do anything - security is automatic when using external: true or target: "_blank".

💡 The link above uses external: true - no need to specify rel or target, it's automatic!

External Links - Automatic Security
# Automatic security (recommended) 
<%= rui_link("https://github.com", "Visit GitHub", external: true) %>
# Renders: <a href="..." target="_blank" rel="noopener noreferrer">Visit GitHub</a> 

# Or use target directly - security attributes added automatically 
<%= rui_link("https://example.com", "Example", target: "_blank") %>
# Also renders: rel="noopener noreferrer"

With Icons

Links support leading and trailing icons.

Links with Icons
<%= rui_link("Download", "#", color: :primary) do |link| %>
  <% link.with_icon(:download) %>
<% end %>

<%= rui_link("Next", "#", color: :primary) do |link| %>
  <% link.with_icon(:arrow_right, position: :trailing) %>
<% end %>

<%= rui_link("Settings", "#", color: :primary) do |link| %>
  <% link.with_icon(:settings) %>
<% end %>

Turbo Integration

Full support for Hotwire Turbo features: Frames, Streams, Methods, Prefetch, and more.

⚡ Turbo-Powered: Build modern, SPA-like experiences without JavaScript. All Turbo features work seamlessly with rui_link.

Basic Turbo Features

Basic Turbo Features
# Turbo Frame - load content into frame 
<%= rui_link("Edit", "/posts/1/edit", turbo_frame: "post_modal") %>

# Turbo Method - DELETE, PATCH, PUT 
<%= rui_link("Delete", "/posts/1",
  turbo_method: :delete,
  turbo_confirm: "Are you sure?",
  color: :danger) %>

# Turbo Stream - stream updates 
<%= rui_link("Load More", "/posts", turbo_stream: true) %>

# Turbo Prefetch - instant navigation (Turbo 8+) 
<%= rui_link("Posts", "/posts", turbo_prefetch: true) %>

# Turbo Action - replace instead of advance 
<%= rui_link("Refresh", "/posts", turbo_action: :replace) %>
# Real-World Example: Post Management

Real-World Example: Blog Post Management

Complete example showing Turbo Frames, Methods, Streams, and Prefetch working together.

posts/index.html.erb
# posts/index.html.erb - Post List with Turbo Frame 
<turbo-frame id="posts-list">
  <% @posts.each do |post| %>
    <div class="post-card border rounded-lg p-4 mb-4">
      <h3 class="text-lg font-bold"><%= post.title %></h3>
      <p class="text-zinc-600 mb-3"><%= post.excerpt %></p>

      <div class="flex gap-2">
        # Edit in modal - loads into "modal" frame 
        <%= rui_link("Edit", edit_post_path(post),
          turbo_frame: "modal",
          color: :primary) do |link|
          link.with_icon(:edit)
        end %>

        # Quick publish/unpublish with Turbo Method 
        <% if post.published? %>
          <%= rui_link("Unpublish", unpublish_post_path(post),
            turbo_method: :patch,
            turbo_confirm: "Unpublish this post?",
            color: :warning) do |link|
            link.with_icon(:eye_off)
          end %>
        <% else %>
          <%= rui_link("Publish", publish_post_path(post),
            turbo_method: :patch,
            color: :success) do |link|
            link.with_icon(:send)
          end %>
        <% end %>

        # Delete with confirmation 
        <%= rui_link("Delete", post_path(post),
          turbo_method: :delete,
          turbo_confirm: "Delete '#{post.title}'? This cannot be undone.",
          color: :danger) do |link|
          link.with_icon(:trash_2)
        end %>

        # View with prefetch for instant navigation 
        <%= rui_link("View", post_path(post),
          turbo_prefetch: true,
          variant: :outline,
          color: :blue) do |link|
          link.with_icon(:eye)
        end %>
      </div>
    </div>
  <% end %>

  # Load more with Turbo Stream 
  <% if @next_page %>
    <%= rui_link("Load More Posts", posts_path(page: @next_page),
      turbo_stream: true,
      variant: :outline,
      full_width: true) do |link|
      link.with_icon(:chevron_down, position: :trailing)
    end %>
  <% end %>
</turbo-frame>

# Modal frame for editing 
<turbo-frame id="modal" class="fixed inset-0 bg-black/50 hidden">
  <%# Edit form loads here when clicking Edit %>
</turbo-frame>

Controller Actions

posts_controller.rb
# posts_controller.rb
class PostsController < ApplicationController
  def publish
    @post = Post.find(params[:id])
    @post.update(published: true)

    # Turbo will automatically update the UI
    redirect_to posts_path, notice: "Post published!"
  end

  def unpublish
    @post = Post.find(params[:id])
    @post.update(published: false)

    redirect_to posts_path, notice: "Post unpublished!"
  end

  def destroy
    @post = Post.find(params[:id])
    @post.destroy

    # Turbo removes the post from the list automatically
    redirect_to posts_path, notice: "Post deleted!"
  end

  def index
    @posts = Post.page(params[:page])
    @next_page = @posts.next_page

    respond_to do |format|
      format.html  # Normal page load
      format.turbo_stream  # "Load More" appends to list
    end
  end
end

Navigation with Instant Loading

Use turbo_prefetch: true for instant navigation between pages.

Navigation with Instant Loading
<%# Navigation with prefetch - feels instant %>
<nav class="flex gap-6">
  <%= rui_link("Home", "/",
    turbo_prefetch: true,
    unless_current: true) %>
  <%= rui_link("Blog", "/posts",
    turbo_prefetch: true,
    unless_current: true) %>
  <%= rui_link("About", "/about",
    turbo_prefetch: true,
    unless_current: true) %>
</nav>

<%# Post detail with previous/next navigation %>
<div class="post-navigation flex justify-between mt-8">
  <% if @post.previous %>
    <%= rui_link(post_path(@post.previous), turbo_prefetch: true) do |link|
      link.with_icon(:arrow_left)
      "← #{@post.previous.title}"
    end %>
  <% end %>

  <%= rui_link("All Posts", posts_path,
    turbo_prefetch: true,
    turbo_action: :replace) %>

  <% if @post.next %>
    <%= rui_link(post_path(@post.next), turbo_prefetch: true) do |link|
      "#{@post.next.title} →"
      link.with_icon(:arrow_right, position: :trailing)
    end %>
  <% end %>
</div>

🎯 Live Interactive Examples

See rui_link with Turbo in action! Visit the Post pages to interact with real working examples.

📝 Post Index

See Turbo Prefetch in navigation links for instant page loads

View All Posts →
👁️ Post Detail

See Turbo Methods (DELETE, PATCH) with confirmations

View Sample Post →
✨ What to Try on Post Pages
  • 🚀 Hover over "View" links - watch the network tab as pages prefetch instantly!
  • Click "Edit" links - they load instantly (thanks to prefetch)
  • 🔥 Try "Publish" link - uses turbo_method: :patch without page reload
  • Try "Delete" link - see Turbo confirmation dialogs in action
  • 🔄 Navigate between posts - experience SPA-like speed!
Quick Prefetch Demo

Hover over these links and watch your browser's network tab - they prefetch on hover!

💡 Pro Tip: Open DevTools Network tab, then hover (don't click) - you'll see the page fetching!

💡 Turbo Best Practices

  • ✅ Use turbo_prefetch for navigation links (instant page loads)
  • ✅ Use turbo_method for quick actions (publish, delete, archive)
  • ✅ Use turbo_frame for modals and inline editing
  • ✅ Use turbo_stream for paginated content (load more, infinite scroll)
  • ✅ Use turbo_confirm for destructive actions (delete, unpublish)
  • ⚠️ Don't use turbo_prefetch on forms or actions that change data

Advanced Features

Additional features for enhanced UX, SEO, and accessibility.

Active State

Highlight the current/active link in navigation or tabs.

Active State
# Active state - underlined, bold, darker color 
<%= rui_link("Active", "#", active: true) %>

# With current_page? helper 
<%= rui_link("Posts", "/posts", active: current_page?("/posts")) %>

Tooltips

Add tooltip text that appears on hover.

Tooltips
# Simple tooltip 
<%= rui_link("API", "/docs", title: "View API Documentation") %>

# Using tooltip alias 
<%= rui_link("API", "/docs", tooltip: "View API Documentation") %>

Text Truncation

Automatically truncate long text with tooltip showing full text.

Text Truncation
# Truncate to 30 chars (default) 
<%= rui_link("/posts/1", @post.very_long_title, truncate: true) %>

# Custom truncation length 
<%= rui_link("/posts/1", @post.title, truncate: 50) %>

Download Links

Create file download links with the download attribute.

Download Links
# Basic download (uses original filename) 
<%= rui_link("Download Report", "/files/report.pdf", download: true) do |link| %>
  <% link.with_icon(:download) %>
<% end %>

# Custom filename for download 
<%= rui_link("Export Data", "/files/data.csv", download: "monthly-export.csv") %>

URL Parameters & Anchors

Build dynamic URLs with query parameters and anchor fragments. Use empty URL with anchor for in-page navigation (table of contents, jump to section, etc).

URL Parameters & Anchors
# Add query parameters
<%= rui_link("Filter", "/posts", params: { category: "rails", sort: "recent" }) %>
# Renders: /posts?category=rails&sort=recent 

# Jump to section on current page (empty URL) 
<%= rui_link("", "Jump to Section", anchor: "my-section") %>
# Renders: #my-section 

# Add anchor to different page 
<%= rui_link("Jump to Advanced", "/docs/link", anchor: "advanced") %>
# Renders: /docs/link#advanced 

# Combine params and anchor 
<%= rui_link("Rails Posts", "/posts", params: { category: "rails" }, anchor: "results") %>
# Renders: /posts?category=rails#results 

# Jump with smooth scroll (for in-page anchors) 
<%= rui_link("", "Table of Contents", anchor: "toc", scroll: :smooth) %>
# Renders: #toc with smooth scroll animation

Full Width Links

Make links stretch to fill their container width, useful for mobile menus and card actions.

Full Width Links
# Full width with icon - content justified between 
<div class="max-w-sm space-y-2">
  <%= rui_link("Dashboard", "/dashboard", full_width: true) do |link| %>
    <% link.with_icon(:arrow_right, position: :trailing) %>
  <% end %>

  <%= rui_link("Settings", "/settings", full_width: true) do |link| %>
    <% link.with_icon(:settings) %>
  <% end %>
</div>

# Useful in mobile navigation or cards 
<div class="max-w-sm">
  <%= rui_link("Admin Panel", "/admin", full_width: true) %>
</div>

Enhanced Rel Attributes

Full control over link relationships for SEO and security.

Enhanced Rel Attributes
# Sponsored/affiliate links
<%= rui_link("https://example.com", "Sponsor", rel: "sponsored", external: true) %>
# Renders: rel="noopener noreferrer sponsored" 

# User-generated content
<%= rui_link("UGC", url, rel: "ugc nofollow", external: true) %>
# Renders: rel="noopener noreferrer ugc nofollow"

Scroll Behavior

Control scroll animation for anchor links.

Scroll Behavior
# Smooth scroll animation
<%= rui_link("Features", "#features", scroll: :smooth) %>

# Instant scroll (no animation)
<%= rui_link("Back to Top", "#top", scroll: :instant) %>

Multilingual Links (HrefLang)

Indicate the language of the linked resource for SEO.

Multilingual Links
<%= rui_link("À propos", "/fr/about", hreflang: "fr") %>
<%= rui_link("Acerca de", "/es/about", hreflang: "es") %>

Referrer Policy

Control what referrer information is sent for privacy.

Referrer Policy
# No referrer (maximum privacy)
<%= rui_link("https://example.com", "Link", referrerpolicy: "no-referrer", external: true) %>

# Send only origin
<%= rui_link("https://example.com", "Link", referrerpolicy: "origin", external: true) %>

Communication Links (Email, Phone, SMS)

First-class support for email, phone, and SMS with full Rails mail_to, phone_to, and sms_to compatibility.

Rails Compatibility: Supports all features from Rails helpers including subject, body, cc, bcc, reply_to, and country_code.

Email Links

Full mail_to compatibility with subject, body, CC, BCC, and reply-to:

Email Links
# Basic email
<%= rui_link(email: "contact@example.com", text: "Email Us") %>

# With subject and body
<%= rui_link(email: "support@example.com",
  subject: "Support Request",
  body: "I need help with my account",
  text: "Get Support") %>

# With CC (string or array)
<%= rui_link(email: "team@example.com",
  cc: "manager@example.com",
  text: "Email Team") %>

<%= rui_link(email: "team@example.com",
  cc: ["manager@example.com", "ceo@example.com"],
  text: "Email Leadership") %>

# With BCC and reply-to
<%= rui_link(email: "newsletter@example.com",
  bcc: "archive@example.com",
  reply_to: "support@example.com",
  text: "Subscribe") %>

# All options combined
<%= rui_link(email: "contact@example.com",
  subject: "Inquiry",
  body: "I have a question",
  cc: "manager@example.com",
  bcc: ["archive@example.com", "log@example.com"],
  reply_to: "support@example.com",
  text: "Contact Sales") %>

Phone Links

Full phone_to compatibility with country code support:

Phone Links
# Basic phone
<%= rui_link(phone: "555-1234", text: "Call Us") %>

# With country code (auto-normalizes +) 
<%= rui_link(phone: "555-1234",
  country_code: "+1",
  text: "Call US") %>

<%= rui_link(phone: "555-1234",
  country_code: "1",  # Auto-adds +
  text: "Call US") %>

# International numbers
<%= rui_link(phone: "20 7946 0958",
  country_code: "+44",
  text: "Call UK Office") %>

SMS Links

Full sms_to compatibility with body and country code:

SMS Links
# Basic SMS
<%= rui_link(sms: "555-1234", text: "Text Us") %>

# With pre-filled message body
<%= rui_link(sms: "555-1234",
  body: "Hello! I'd like more information",
  text: "Send SMS") %>

# With country code and body
<%= rui_link(sms: "555-1234",
  country_code: "+1",
  body: "Quick question about your product",
  text: "Send Message") %>

Platform Compatibility

  • Email: All options work in all major email clients
  • Phone: Country codes work universally with tel: protocol
  • ⚠️ SMS body: iOS (full support), Android (partial), Desktop (limited)

Real-World Examples

See how links work in common UI patterns and real-world scenarios.

Link Within Paragraph

RapidRailsUI is a ViewComponent-based UI library. Learn more about it in our documentation or check out the https://github.com on GitHub.

Link in Paragraph
<p>
  Learn more in our <%= rui_link("documentation", "/docs") %> or
  check the <%= rui_link("https://github.com", "code", external: true) %>
</p>

Icon Links

Perfect for actions and navigation.

Icon Links
<%= rui_link("#") do |link| %>
  <% link.with_icon(:download) %>
  Download PDF
<% end %>

Call-to-Action Links

Prominent links for important actions.

CTA Links
<% rui_link("/signup", size: :lg, color: :primary) do |link| %>
  Get Started
  <% link.with_icon(:arrow_right) %>
<% end %>

<% rui_link("/join", size: :lg, color: :blue) do |link| %>
  Ready to join?
  <% link.with_icon(:door_open, position: :trailing, color: :red) %>
<% end %>

<% rui_link("/pricing", size: :lg, color: :success) do |link| %>
  <% link.with_icon(:credit_card) %>
  View Pricing
<% end %>

Card with Action Link

Cards with prominent action links.

Featured Post

Learn how to build amazing Rails applications with our latest tutorial.

Read more
Card with Link
<div class="card">
  <h4>Featured Post</h4>
  <p>Description...</p>
  <% rui_link("/posts/1") do |link| %>
    Read more
    <% link.with_icon(:arrow_right, position: :trailing) %>
  <% end %>
</div>

Navigation with Active States

Using unless_current for navigation menus.

Navigation with Active States
<nav>
  <%= rui_link("Home", "/", unless_current: true) %>
  <%= rui_link("About", "/about", unless_current: true) %>
  <%= rui_link("Blog", "/blog", unless_current: true) %>
</nav>

List of Related Links

Common pattern for resource lists.

List of Related Links
<ul>
  <li>
    <% rui_link("/docs/button", variant: :hover_underline) do |link| %>
      <% link.with_icon(:circle) %>
      Button Component
    <% end %>
  </li>
</ul>

Footer Links

Clean, accessible footer navigation.

Footer Links
<footer>
  <h5>Product</h5>
  <ul>
    <li><%= rui_link("Features", "/features", variant: :hover_underline) %></li>
    <li><%= rui_link("Pricing", "/pricing", variant: :hover_underline) %></li>
  </ul>
</footer>

Accessibility

Links are built with accessibility as a priority, following WCAG guidelines and best practices.

ARIA Attributes

  • aria-disabled="true" and tabindex="-1" when disabled
  • aria-busy="true" when in loading state
  • aria-hidden="true" on decorative icons (loading spinner, external indicator)
  • title or aria-label for icon-only links
  • hreflang attribute for multilingual resource hints

Keyboard Navigation

  • Tab to focus, Enter to activate
  • Visible focus ring via focus-visible:ring-2
  • Disabled links removed from tab order with tabindex="-1"

Semantic HTML

  • Native <a> elements with proper href attributes
  • External links include rel="noopener noreferrer" for security
  • All color variants meet WCAG contrast requirements
  • Truncated text auto-generates tooltip with full content

Screen Reader Support

  • Use descriptive link text (avoid "click here")
  • External link indicator visually communicates target opens in new tab
  • Language hints via hreflang help assistive technologies

Example: Accessible Link Patterns

Accessible Link Examples
# Descriptive link text (good)
<%= rui_link("View pricing plans", "/pricing") %>

# Icon-only link with title for accessibility
<%= rui_link("/settings", title: "Account Settings") do |link| %>
  <% link.with_icon(:settings) %>
<% end %>

# External link with automatic security attributes
<%= rui_link("https://github.com", "View on GitHub", external: true) %>
# Renders: rel="noopener noreferrer" target="_blank"

# Multilingual link
<%= rui_link("À propos", "/fr/about", hreflang: "fr") %>

# Disabled link (removed from tab order)
<%= rui_link("Admin Panel", "/admin", disabled: true) %>
# Renders: aria-disabled="true" tabindex="-1"

API Reference

Appearance

Visual styling options

Parameter Type Default Description
variant Symbol :hover_underline Link style variant
:underline :no_underline :hover_underline :animated_underline :nav
color Symbol :blue Link color - semantic or Tailwind (nav defaults to :neutral)
size Symbol :base Font size
:xs :sm :base :lg :xl
full_width Boolean false Stretch link to full container width

Conditional Rendering

Control when link is rendered

Parameter Type Default Description
if Boolean/Proc Render link only if truthy
unless Boolean/Proc Render link unless truthy
unless_current Boolean false Render as text if current page

Turbo Integration

Hotwire Turbo options

Parameter Type Default Description
turbo_frame String Target Turbo Frame ID
turbo_stream Boolean false Accept Turbo Stream response
turbo_method Symbol HTTP method for Turbo
:get :post :put :patch :delete
turbo_prefetch Boolean Enable/disable Turbo prefetch
turbo_confirm String Confirmation message

URL Building

Build dynamic URLs with parameters and anchors

Parameter Type Default Description
params Hash Query parameters to append to URL
anchor String URL anchor/fragment (#section)
download Boolean/String Enable download; optionally specify filename

External Links

Security options for external links

Parameter Type Default Description
external Boolean auto-detected Force external link behavior
target String "_blank" for external Link target
rel String "noopener noreferrer" for external Link rel attribute

States

State options

Parameter Type Default Description
disabled Boolean false Disable the link
active Boolean false Active state styling

Slots

Content slots for customizing component parts

Slot Description
icon Icon slot via link.with_icon(:name, position: :leading/:trailing)

Related Components