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.


<%= rui_link("/posts", "View Posts") %>

<%= rui_link("/posts", "View Posts", color: :primary) %>

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

<%= 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.

<%= rui_link("#", "Underline", variant: :underline) %>
<%= rui_link("#", "No Underline", variant: :no_underline) %>
Hover Underline Default
<%= 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.


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

Colored Navigation Sections

Use colors to create themed navigation sections:

Default (Neutral)

Blue Section

Green Section


<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:

<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:


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

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

Sizes

Five size options from xs to xl.

<%= 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.


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

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

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

States

Disabled and loading states are supported.

<%= 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 MDN recommendations , 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!


<%= rui_link("https://github.com", "Visit GitHub", external: true) %>

<%= rui_link("https://example.com", "Example", target: "_blank") %>

With Icons

Links support leading and trailing 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


<%= rui_link("/posts/1/edit", "Edit", turbo_frame: "post_modal") %>

<%= rui_link("/posts/1", "Delete",
  turbo_method: :delete,
  turbo_confirm: "Are you sure?",
  color: :danger) %>

<%= rui_link("/posts", "Load More", turbo_stream: true) %>

<%= rui_link("/posts", "Posts", turbo_prefetch: true) %>

<%= rui_link("/posts", "Refresh", turbo_action: :replace) %>

Real-World Example: Blog Post Management

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


<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">
        <%= rui_link(edit_post_path(post), "Edit",
          turbo_frame: "modal",
          color: :primary) do |link|
          link.with_icon(:edit)
        end %>

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

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

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

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

<turbo-frame id="modal" class="fixed inset-0 bg-black/50 hidden">
</turbo-frame>

Controller Actions


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

    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

    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  
      format.turbo_stream  
    end
  end
end

Navigation with Instant Loading

Use turbo_prefetch: true for instant navigation between pages.


<nav class="flex gap-6">
  <%= rui_link("/", "Home",
    turbo_prefetch: true,
    unless_current: true) %>
  <%= rui_link("/posts", "Blog",
    turbo_prefetch: true,
    unless_current: true) %>
  <%= rui_link("/about", "About",
    turbo_prefetch: true,
    unless_current: true) %>
</nav>

<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(posts_path, "All Posts",
    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.


<%= rui_link("#", "Active", active: true) %>

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

Tooltips

Add tooltip text that appears on hover.

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

Text Truncation

Automatically truncate long text with tooltip showing full text.


<%= rui_link("/posts/1", @post.very_long_title, truncate: true) %>

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

Download Links

Create file download links with the download attribute.


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

<%= rui_link("/files/data.csv", "Export Data", 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).


<%= rui_link("/posts", "Filter", params: { category: "rails", sort: "recent" }) %>

<%= rui_link("", "Jump to Section", anchor: "my-section") %>

<%= rui_link("/docs/link", "Jump to Advanced", anchor: "advanced") %>

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

<%= rui_link("", "Table of Contents", anchor: "toc", scroll: :smooth) %>

Full Width Links

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


<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>

<div class="max-w-sm">
  <%= rui_link("/admin", "Admin Panel", full_width: true) %>
</div>

Enhanced Rel Attributes

Full control over link relationships for SEO and security.


<%= rui_link("https://example.com", "Sponsor", rel: "sponsored", external: true) %>

<%= rui_link(url, "UGC", rel: "ugc nofollow", external: true) %>

Scroll Behavior

Control scroll animation for anchor links.


<%= rui_link("#features", "Features", scroll: :smooth) %>

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

Multilingual Links (HrefLang)

Indicate the language of the linked resource for SEO.

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

Referrer Policy

Control what referrer information is sent for privacy.


<%= rui_link("https://example.com", "Link", referrerpolicy: "no-referrer", external: true) %>

<%= 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:


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

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

<%= 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") %>

<%= rui_link(email: "newsletter@example.com",
  bcc: "archive@example.com",
  reply_to: "support@example.com",
  text: "Subscribe") %>

<%= 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:


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

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

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

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

SMS Links

Full sms_to compatibility with body and country code:


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

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

<%= 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 source code on GitHub.

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

Icon Links

Perfect for actions and navigation.

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

Call-to-Action Links

Prominent links for important actions.

<% 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
<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.

<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.

<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>
  <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("/pricing", "View pricing plans") %>

<%# 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("/fr/about", "À propos", hreflang: "fr") %>

<%# Disabled link (removed from tab order) %>
<%= rui_link("/admin", "Admin Panel", 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