Link
A link component that renders <a> elements with conditional rendering support and full Turbo integration.
Key Features
- Conditional Rendering, Support for
if:,unless:, andunless_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.
# 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.
<%= 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.
<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
<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:
# 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.
<%= 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 Panel", "/admin", if: admin?, color: :primary) %%>
<%= rui_link("Login", "/login", unless: user_signed_in?) %%>
<%= rui_link("Posts", posts_path, unless_current: true) %%>
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 https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/a#security_and_privacy , external links automatically include:
rel="noopener"- Preventswindow.openerexploitation (tabnabbing attacks)rel="noreferrer"- Blocks referrer information from being senttarget="_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!
# 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.
<%= 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
# 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: Blog Post Management
Complete example showing Turbo Frames, Methods, Streams, and Prefetch working together.
# 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
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 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.
✨ 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: :patchwithout 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_prefetchfor navigation links (instant page loads) - ✅ Use
turbo_methodfor quick actions (publish, delete, archive) - ✅ Use
turbo_framefor modals and inline editing - ✅ Use
turbo_streamfor paginated content (load more, infinite scroll) - ✅ Use
turbo_confirmfor destructive actions (delete, unpublish) - ⚠️ Don't use
turbo_prefetchon 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 - 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.
# 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.
# 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.
# 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).
# 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 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.
# 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.
# 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.
<%= 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.
# 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:
# 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:
# 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:
# 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.
<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.
<%= 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.
<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"andtabindex="-1"when disabled -
aria-busy="true"when in loading state -
aria-hidden="true"on decorative icons (loading spinner, external indicator) -
titleoraria-labelfor icon-only links -
hreflangattribute for multilingual resource hints
Keyboard Navigation
-
Tabto focus,Enterto activate -
Visible focus ring via
focus-visible:ring-2 -
Disabled links removed from tab order with
tabindex="-1"
Semantic HTML
-
Native
<a>elements with properhrefattributes -
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
hreflanghelp assistive technologies
Example: Accessible Link Patterns
# 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
rui_link
Link component with conditional rendering and full Turbo integration
| Parameter | Type | Default | Description |
|---|---|---|---|
| url* | String | — | Link URL (positional or keyword) |
| text | String | — | Link text (positional or keyword, or use block) |
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) |