ComponentsThemes
Palette
Default
Stone
Rose
Blue
Green
Violet
Yellow
Orange
6

← Components

Skeleton

Loading placeholder — a pulsing muted box you size with utility classes.

Installation

bin/rails g wabi:add skeleton

Example

Source

app/components/ui/skeleton.rb

# frozen_string_literal: true

module Components
  module UI
    class Skeleton < Wabi::Base
      variants do
        base "animate-pulse motion-reduce:animate-none rounded-md bg-muted"
      end

      def initialize(**attrs) = @attrs = attrs

      def view_template(&)
        user_class = @attrs.delete(:class)
        # role="status" is a live region that legitimately supports an accessible name,
        # so aria-label is valid here (a bare <div> is role=generic and prohibits it) and
        # AT announces the loading state. aria-busy signals loading; aria-label describes it.
        # Callers can override any of these via **attrs (e.g. aria_label: "Loading avatar").
        div(
          role: "status",
          "aria-busy": "true",
          "aria-label": "Loading…",
          **@attrs,
          class: merge_class(tokens, user_class),
          &
        )
      end
    end
  end
end

Accessibility

  • It's a decorative placeholder: the root sets aria-busy="true" and a default aria-label="Loading…" (override via aria_label:) so assistive tech knows the region is loading rather than empty.
  • Mark the surrounding region that swaps in the real content with role="status" or aria-live="polite" so screen readers announce when loading completes.
  • The animate-pulse animation is paired with motion-reduce:animate-none, so it stops for users who prefer reduced motion.