Image

A semantic HTML image component for displaying content images with captions, links, and visual effects.

Key Features

  • Semantic HTML - Uses <figure> and <figcaption> when caption present
  • Picture Element - Art direction and format fallback with <picture> and <source>
  • Performance - Built-in lazy loading, async decoding, and fetchpriority
  • Responsive - Support for srcset and sizes attributes
  • Visual Effects - Grayscale, blur, sepia, zoom, shine, overlay
  • Flexible Layout - Multiple sizes, shapes, aspect ratios, and alignments
  • Linkable - Wrap images in links with proper styling
  • Accessibility - Proper alt text and semantic structure
  • Social Media Sizes - Built-in presets for Instagram, Facebook, X, LinkedIn, Pinterest, TikTok, YouTube

Basic Usage

The image component renders optimized images with semantic HTML. The src can be passed as a positional argument (recommended) or keyword argument.

Basic Usage
<%# Simple image (positional src argument - recommended) %>
<%= rui_image("photo.jpg", alt: "A beautiful sunset") %>

<%# Alternative: keyword argument for src %>
<%= rui_image(src: "photo.jpg", alt: "A beautiful sunset") %>

<%# Image with caption (uses figure/figcaption) %>
<%= rui_image(
  "landscape.jpg",
  alt: "Mountain vista",
  caption: "View from the summit at sunrise"
) %>

<%# Linked image %>
<%= rui_image(
  "product.jpg",
  alt: "Product name",
  url: product_path(@product)
) %>
Mountain landscape

Simple image with lazy loading and async decoding

Semantic HTML (Figure & Caption)

When you provide a caption, the image automatically uses semantic <figure> and <figcaption> elements.

Without Caption

Nature scene
<img src="..." alt="...">

With Caption

Foggy mountains
Morning mist over the valley
<figure>
  <img src="..." alt="...">
  <figcaption>...</figcaption>
</figure>
Image with Caption
<%= rui_image(
  "photo.jpg",
  alt: "Sunset over the ocean",
  caption: "Malibu Beach - Summer 2024"
) %>

Sizes

Control image dimensions with preset sizes.

Ocean

xs (64px)

Ocean

sm (96px)

Ocean

md (128px)

Ocean

lg (192px)

Size Options
<%= rui_image(url, alt: "Photo", size: :xs) %>   <%# 64px %>
<%= rui_image(url, alt: "Photo", size: :sm) %>   <%# 96px %>
<%= rui_image(url, alt: "Photo", size: :md) %>   <%# 128px %>
<%= rui_image(url, alt: "Photo", size: :lg) %>   <%# 192px %>
<%= rui_image(url, alt: "Photo", size: :xl) %>   <%# 256px %>
<%= rui_image(url, alt: "Photo", size: :full) %> <%# 100% width %>

Social Media Platform Sizes

Built-in presets for common social media image dimensions (based on 2025 guidelines). These presets include the correct aspect ratio automatically.

Note

When using platform sizes, the aspect ratio is automatically applied. The ratio parameter is ignored for platform sizes.

Instagram

Instagram post

:ig_post (1:1)

Instagram portrait

:ig_post_portrait (4:5)

Instagram story

:ig_story (9:16)

Instagram landscape

:ig_post_landscape (1.91:1)

Facebook

Facebook post

:fb_post (1:1)

Facebook story

:fb_story (9:16)

Facebook cover

:fb_cover (2.7:1)

X (Twitter)

X post

:x_post (16:9)

X post square

:x_post_square (1:1)

X header

:x_header (3:1)

LinkedIn

LinkedIn post

:linkedin_post (1.91:1)

LinkedIn square

:linkedin_post_square (1:1)

LinkedIn cover

:linkedin_cover (5.9:1)

Other Platforms

Pinterest pin

:pin (2:3)

TikTok video

:tiktok_post (9:16)

YouTube thumbnail

:yt_thumbnail (16:9)

Pinterest square

:pin_square (1:1)

Social Media Platform Sizes
<%# Instagram %>
<%= rui_image("campaign.jpg", alt: "Summer campaign", size: :ig_post) %>
<%= rui_image("story.jpg", alt: "New product", size: :ig_story) %>
<%= rui_image("reel.jpg", alt: "Tutorial", size: :ig_reel) %>

<%# Facebook %>
<%= rui_image("post.jpg", alt: "Announcement", size: :fb_post) %>
<%= rui_image("cover.jpg", alt: "Company cover", size: :fb_cover) %>

<%# X (Twitter) %>
<%= rui_image("news.jpg", alt: "Breaking news", size: :x_post) %>
<%= rui_image("banner.jpg", alt: "Profile banner", size: :x_header) %>

<%# LinkedIn %>
<%= rui_image("article.jpg", alt: "Industry insights", size: :linkedin_post) %>

<%# Pinterest %>
<%= rui_image("recipe.jpg", alt: "Recipe idea", size: :pin) %>

<%# TikTok %>
<%= rui_image("video.jpg", alt: "Dance tutorial", size: :tiktok_post) %>

<%# YouTube %>
<%= rui_image("thumb.jpg", alt: "Video title", size: :yt_thumbnail) %>

Shapes

Four shape options for different visual styles.

Book

Default

shape: :default
Book

Rounded

shape: :rounded
Book

Circle

shape: :circle
Book

Square

shape: :square

Aspect Ratios

Force images to specific aspect ratios for consistent layouts.

Mountain

ratio: :square (1:1)

Mountain

ratio: :landscape (4:3)

Mountain

ratio: :video (16:9)

Aspect Ratio Options
<%= rui_image(url, alt: "Photo", ratio: :square) %>    <%# 1:1 %>
<%= rui_image(url, alt: "Photo", ratio: :video) %>     <%# 16:9 %>
<%= rui_image(url, alt: "Photo", ratio: :portrait) %>  <%# 3:4 %>
<%= rui_image(url, alt: "Photo", ratio: :landscape) %> <%# 4:3 %>
<%= rui_image(url, alt: "Photo", ratio: :wide) %>      <%# 16:9 %>
<%= rui_image(url, alt: "Photo", ratio: :ultrawide) %> <%# 21:9 %>

Object Fit

Control how images fill their container.

Nature

fit: :cover

Nature

fit: :contain

Nature

fit: :fill

Object Fit Options
<%= rui_image(url, alt: "Photo", fit: :cover) %>      <%# Crop to fill (default) %>
<%= rui_image(url, alt: "Photo", fit: :contain) %>    <%# Letterbox %>
<%= rui_image(url, alt: "Photo", fit: :fill) %>       <%# Stretch %>
<%= rui_image(url, alt: "Photo", fit: :none) %>       <%# Natural size %>
<%= rui_image(url, alt: "Photo", fit: :scale_down) %> <%# Shrink only %>

Visual Effects

Apply hover-activated visual effects to images. Hover over the images below to see the effects.

Portrait

grayscale: true

Hover to see color

Portrait

blur: true

Hover to unblur

Portrait

sepia: true

Hover for color

Portrait

zoom: true

Hover to zoom

Portrait

shine: true

Hover for shine

Portrait

overlay: true

Hover for overlay

Combining Visual Effects
<%# Combine multiple effects %>
<%= rui_image(
  "photo.jpg",
  alt: "Team member",
  grayscale: true,
  zoom: true,
  shape: :rounded
) %>

<%# Professional team photo with grayscale + zoom %>
<%= rui_image(
  @member.photo_url,
  alt: @member.name,
  grayscale: true,
  zoom: true,
  shape: :circle
) %>

Linked Images

Make images clickable by providing a URL.

Product image
Watch
Premium Watch - $299

Click the images (they're linked)

Linked Images
<%= rui_image(
  @product.image_url,
  alt: @product.name,
  url: product_path(@product),
  shape: :rounded,
  zoom: true
) %>

<%# With caption %>
<%= rui_image(
  @product.image_url,
  alt: @product.name,
  caption: "#{@product.name} - #{number_to_currency(@product.price)}",
  url: product_path(@product)
) %>

Performance Attributes

Built-in performance optimization with lazy loading and async decoding.

Default Behavior (Optimized)

  • loading="lazy" - Defers loading until near viewport
  • decoding="async" - Non-blocking image decode
  • fetchpriority="auto" - Browser decides priority

Above-the-Fold Images (Hero)

For images visible immediately on page load, disable lazy loading:

Hero Image (Eager Loading)
<%# Hero image - load immediately %>
<%= rui_image(
  "hero-banner.jpg",
  alt: "Welcome to our store",
  size: :full,
  loading: :eager,
  fetchpriority: :high,
  decoding: :sync
) %>

Prevent Layout Shift

Always provide width and height to prevent content jumping:

Prevent Layout Shift
<%= rui_image("photo.jpg", alt: "Photo", width: 800, height: 600) %>

Responsive Images

Serve different image sizes for different screen widths.

Responsive srcset
<%= rui_image(
  "photo.jpg",
  alt: "Responsive photo",
  srcset: "photo-320.jpg 320w, photo-640.jpg 640w, photo-1280.jpg 1280w",
  sizes: "(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 33vw",
  width: 1280,
  height: 960
) %>

Cross-Origin Images

For images from CDNs or other domains:

Cross-Origin Images (CORS)
<%= rui_image(
  "https://cdn.example.com/image.jpg",
  alt: "CDN image",
  crossorigin: "anonymous",
  referrerpolicy: "no-referrer"
) %>

Picture Element

The <picture> element provides advanced image handling for art direction and format fallback. Unlike srcset on a regular image (which serves different sizes of the same image), <picture> allows serving completely different images for different scenarios.

When to Use Picture vs srcset

  • srcset on img: Same image, different sizes (resolution switching)
  • picture: Different images for different viewports (art direction)
  • picture: Modern format (AVIF/WebP) with JPEG fallback
  • picture: Different images for dark/light mode

Format Fallback (AVIF/WebP with JPEG fallback)

Serve modern, smaller formats to browsers that support them, with automatic fallback. The images look identical but have different file sizes.

Note

Format fallback requires actual different format files on your server. The browser picks the first supported format. Images appear identical - only the file size/quality differs. Check DevTools Network tab to see which format loaded.

Format Fallback
<%# AVIF → WebP → JPEG (images look the same, different sizes) %>
<%= rui_image(
  "photo.jpg",
  alt: "High quality photo",
  sources: [
    { srcset: "photo.avif", type: "image/avif" },
    { srcset: "photo.webp", type: "image/webp" }
  ]
) %>

Art Direction (Different Images for Different Viewports)

Show completely different images based on screen size. Resize your browser window to see the image change:

Responsive hero - resize browser to see different images

≥1024px: Foggy valley (wide landscape)

640-1023px: Forest path (medium)

<640px: Mountain peaks (portrait - fallback)

Try it!

Drag your browser window smaller/larger to see completely different images load. This is art direction - not just resizing the same image.

Art Direction
<%# Different images per viewport %>
<%= rui_image(
  "mobile-portrait.jpg",
  alt: "Product showcase",
  sources: [
    { srcset: "desktop-wide.jpg", media: "(min-width: 1024px)" },
    { srcset: "tablet-medium.jpg", media: "(min-width: 640px)" }
  ]
) %>

Dark Mode Images

There are two approaches for dark mode images, depending on how your site handles theming:

Approach 1: CSS-based (works with site theme toggle)

If your site uses a JavaScript theme toggle (like data-theme="dark"), use Tailwind's dark: classes to show/hide images:

Sunny beach (light mode)

Light theme: Sunny beach | Dark theme: Starry night sky

Try it!

Toggle the site's dark mode (top right) to see the image change. This works with JavaScript theme toggles.

CSS-based Dark Mode (Recommended)
<%# Works with JavaScript theme toggles %>
<div class="relative">
  <%# Light mode image %>
  <div class="block dark:hidden">
    <%= rui_image("logo-light.png", alt: "Logo") %>
  </div>
  <%# Dark mode image %>
  <div class="hidden dark:block">
    <%= rui_image("logo-dark.png", alt: "Logo") %>
  </div>
</div>

Approach 2: Picture element (OS preference only)

The prefers-color-scheme media query only responds to the operating system's dark mode setting, not JavaScript theme toggles:

Important

prefers-color-scheme: dark only detects your OS dark mode setting (System Preferences → Appearance). It does NOT respond to website theme toggles that use data-theme or class-based switching.

Picture Element (OS Preference Only)
<%# Only works with operating system dark mode setting %>
<%= rui_image(
  "logo-light.png",
  alt: "Company logo",
  sources: [
    { srcset: "logo-dark.png", media: "(prefers-color-scheme: dark)" }
  ]
) %>

Picture Element with Caption

Combine picture element with semantic figure/figcaption. Resize your browser to see different images with the same caption:

Team member - responsive portrait
Sarah Chen - CEO & Co-founder

≥768px: Wide banner crop | <768px: Square portrait

Picture with Caption
<%= rui_image(
  "team-member-square.jpg",
  alt: "Team member portrait",
  caption: "Sarah Chen - CEO & Co-founder",
  sources: [
    { srcset: "team-member-wide.jpg", media: "(min-width: 768px)" }
  ],
  shape: :rounded,
  alignment: :center
) %>

Source Options Reference

Key Required Description
srcset Yes Image source(s) for this source element
media No Media query (e.g., "(min-width: 1024px)", "(prefers-color-scheme: dark)")
type No MIME type (e.g., "image/avif", "image/webp")
sizes No Responsive sizes for this source
width / height No Dimension hints for the browser

Responsive srcset within Source

Combine art direction with responsive sizing. Each source can have its own srcset with multiple resolutions:

Art Direction + Responsive Sizes
<%# Different images per viewport, each with multiple sizes %>
<%= rui_image(
  "hero-mobile.jpg",
  alt: "Hero banner",
  sources: [
    {
      srcset: "hero-desktop-800.jpg 800w, hero-desktop-1200.jpg 1200w, hero-desktop-1600.jpg 1600w",
      media: "(min-width: 1024px)",
      sizes: "(min-width: 1400px) 1400px, 100vw"
    },
    {
      srcset: "hero-tablet-600.jpg 600w, hero-tablet-900.jpg 900w",
      media: "(min-width: 640px)",
      sizes: "100vw"
    }
  ],
  width: 1600,
  height: 900
) %>

High DPI / Retina Images

Serve higher resolution images for retina displays using density descriptors (1x, 2x, 3x):

Retina / High DPI
<%# Serve 2x and 3x images for high DPI displays %>
<%= rui_image(
  "logo.png",
  alt: "Company logo",
  sources: [
    { srcset: "logo.png 1x, logo@2x.png 2x, logo@3x.png 3x" }
  ]
) %>

<%# Combined with art direction %>
<%= rui_image(
  "product-mobile.jpg",
  alt: "Product image",
  sources: [
    {
      srcset: "product-desktop.jpg 1x, product-desktop@2x.jpg 2x",
      media: "(min-width: 768px)"
    },
    {
      srcset: "product-mobile.jpg 1x, product-mobile@2x.jpg 2x"
    }
  ]
) %>

Combined: Art Direction + Format Fallback

The most advanced use case - different images per viewport, each with modern format fallbacks:

Art Direction + Format Fallback
<%# Different images for desktop/mobile, with AVIF/WebP fallback %>
<%= rui_image(
  "hero-mobile.jpg",
  alt: "Product showcase",
  sources: [
    # Desktop: wide image with format fallback
    { srcset: "hero-desktop.avif", media: "(min-width: 1024px)", type: "image/avif" },
    { srcset: "hero-desktop.webp", media: "(min-width: 1024px)", type: "image/webp" },
    { srcset: "hero-desktop.jpg", media: "(min-width: 1024px)" },
    # Tablet: medium image with format fallback
    { srcset: "hero-tablet.avif", media: "(min-width: 640px)", type: "image/avif" },
    { srcset: "hero-tablet.webp", media: "(min-width: 640px)", type: "image/webp" },
    { srcset: "hero-tablet.jpg", media: "(min-width: 640px)" },
    # Mobile: format fallback only (no media query)
    { srcset: "hero-mobile.avif", type: "image/avif" },
    { srcset: "hero-mobile.webp", type: "image/webp" }
  ]
) %>

Generated HTML

HTML Output
<picture>
  <source srcset="photo.avif" type="image/avif">
  <source srcset="photo.webp" type="image/webp">
  <img src="photo.jpg" alt="Description" loading="lazy" decoding="async">
</picture>

Real-World Examples

Product Gallery

Product Gallery
<div class="grid grid-cols-4 gap-4">
  <% @products.each do |product| %>
    <%= rui_image(
      product.image_url,
      alt: product.name,
      ratio: :square,
      shape: :rounded,
      zoom: true,
      url: product_path(product)
    ) %>
  <% end %>
</div>

Team Member Grid

Sarah Chen
Sarah Chen

CEO

Mike Johnson
Mike Johnson

CTO

Emma Wilson
Emma Wilson

Designer

Alex Rivera
Alex Rivera

Engineer

Team Member Grid
<div class="grid grid-cols-4 gap-6">
  <% @team_members.each do |member| %>
    <div class="text-center">
      <%= rui_image(
        member.photo_url,
        alt: member.name,
        caption: member.name,
        size: :lg,
        shape: :circle,
        grayscale: true,
        alignment: :center
      ) %>
      <%= rui_text(member.role, size: :sm, color: :muted, class: "mt-1") %>
    </div>
  <% end %>
</div>

Blog Post Image

How We Built Our Design System

Computer setup with code on screen
Our engineering team's workspace

Building a comprehensive design system requires careful planning and collaboration across teams...

Accessibility

The Image component follows WAI-ARIA best practices for accessible images.

ARIA Attributes

  • alt attribute required for meaningful images (describes content)
  • alt="" for decorative images (hidden from screen readers)
  • role="img" implicit on <img> elements
  • Linked images inherit accessible name from alt text

Keyboard Navigation

  • Linked images are focusable via Tab
  • Enter activates linked images
  • Focus ring visible on linked images for keyboard users

Semantic HTML

  • Native <img> element for standard images
  • <figure> + <figcaption> for images with captions
  • <picture> element for responsive art direction
  • Proper loading="lazy" and decoding="async" for performance

Screen Reader Support

  • Alt text announced when image is encountered
  • Captions associated with images via <figcaption>
  • Linked images announced as "link, image" with alt text

Example: Accessible Image Patterns

Accessible Image Examples
<%# Meaningful image with descriptive alt text %>
<%= rui_image("team-photo.jpg", alt: "Marketing team celebrating product launch") %>

<%# Decorative image (hidden from screen readers) %>
<%= rui_image("decorative-border.png", alt: "") %>

<%# Image with caption (uses figure/figcaption) %>
<%= rui_image(
  "chart.png",
  alt: "Sales growth chart showing 45% increase",
  caption: "Q4 2024 sales performance by region"
) %>

<%# Linked image (alt text describes destination) %>
<%= rui_image(
  "product.jpg",
  alt: "View Blue Widget product details",
  url: product_path(@product)
) %>

API Reference

rui_image

Semantic HTML image with captions, links, visual effects, and picture element support

Parameter Type Default Description
src* String Image source URL (positional or keyword)
alt* String Alt text for accessibility
caption String Caption text (triggers figure/figcaption)
url String Link URL (makes image clickable)

Sizing

Size and dimension options

Parameter Type Default Description
size Symbol :auto Size preset
:auto :full :xs :sm :base :lg :xl :xl2 :xl3
width Integer/String Width in pixels or CSS value
height Integer/String Height in pixels or CSS value
aspect Symbol Aspect ratio
:square :video :wide :portrait :auto

Appearance

Visual styling options

Parameter Type Default Description
shape Symbol :square Corner style
:square :rounded :pill :circle
fit Symbol :cover Object fit behavior
:cover :contain :fill :none :scale_down
filter Symbol CSS filter effect
:grayscale :sepia :blur :brightness :contrast
hover_effect Symbol Hover effect
:zoom :brighten :darken :grayscale_to_color

Performance

Loading and performance options

Parameter Type Default Description
loading Symbol :lazy Loading behavior
:lazy :eager
srcset String Responsive source set
sizes String Responsive sizes attribute
sources Array Picture element sources for art direction

Platform Sizes

Social media preset sizes

Parameter Type Default Description
:ig_post Preset Instagram post (1080x1080)
:ig_story Preset Instagram story (1080x1920)
:fb_cover Preset Facebook cover (820x312)
:twitter_header Preset Twitter header (1500x500)
:og_image Preset Open Graph (1200x630)

Image vs Avatar

Use Image for:

  • Content images (photos, illustrations)
  • Images with captions
  • Gallery images
  • Product images
  • Hero banners
  • Blog post images

Use Avatar for:

  • User profile pictures
  • Team member photos (no caption)
  • Status indicators (online/offline)
  • Grouped/stacked avatars
  • Fallback to initials or icon
  • Social media platform sizes

Related Components