Senren UI
Components / App Shell

App Shell

Stable

SaaS dashboard layout root

Skip to content
Senren Workspace

Workspace

A compact application frame for SaaS screens.

Active projects

24

+6 this week

Open tasks

128

-12 since Monday

Usage example #

Copy this ERB into a Rails view after installing the component. The snippet below is the same code used by the live preview above.

app/views/.../app_shell_example.html.erb
<div class="w-full max-w-2xl">
  <%= render Senren::AppShellComponent.new(content_id: "preview-main", variant: :compact) do |app| %>
    <% app.with_top_nav do %>
      <%= render Senren::TopNavComponent.new(
        items: [["Overview", "#"], ["Members", "#"], ["Billing", "#"]],
        current_path: "#",
        variant: :solid
      ) do |nav| %>
        <% nav.with_brand { "Senren Workspace" } %>
      <% end %>
    <% end %>

    <% app.with_sidebar do %>
      <nav aria-label="Workspace" class="space-y-1 rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-card))] p-2 text-sm">
        <% ["Dashboard", "Projects", "Settings"].each_with_index do |item, index| %>
          <a href="#" class="block rounded-(--senren-radius) px-3 py-2 <%= index.zero? ? 'bg-[hsl(var(--senren-accent))] text-[hsl(var(--senren-accent-foreground))]' : 'text-[hsl(var(--senren-muted-foreground))] hover:bg-[hsl(var(--senren-muted)/0.5)] hover:text-[hsl(var(--senren-foreground))]' %>"><%= item %></a>
        <% end %>
      </nav>
    <% end %>

    <% app.with_header do %>
      <%= render Senren::PageHeaderComponent.new(title: "Workspace", description: "A compact application frame for SaaS screens.") %>
    <% end %>

    <div class="grid gap-3 sm:grid-cols-2">
      <%= render Senren::StatCardComponent.new(label: "Active projects", value: "24", change: "+6", helper_text: "this week", variant: :success) %>
      <%= render Senren::StatCardComponent.new(label: "Open tasks", value: "128", change: "-12", helper_text: "since Monday") %>
    </div>
  <% end %>
</div>

Install this component #

Copy the official component into your app

Use this when you want the Senren-maintained implementation copied into app/components/senren.

Terminal
bin/rails senren:add app_shell

Create a custom component with the same conventions

Use this when you need an app-specific static component that follows Senren's ViewComponent structure.

Terminal
bin/rails generate senren:component app_shell --no-client

Dependencies are resolved by senren:add: sidebar, top_nav.

At a glance #

Category Saas
Class name Senren::AppShellComponent
Stimulus
Variants default, compact
Depends on sidebar, top_nav
Pairs with page_header

Source #

app/components/senren/app_shell_component.html.erb
<%= tag.div(**root_attrs("min-h-screen text-[hsl(var(--senren-foreground))]")) do %>
  <a href="#<%= content_id %>" class="sr-only focus:not-sr-only focus:fixed focus:left-4 focus:top-4 focus:z-50 focus:rounded-(--senren-radius) focus:bg-[hsl(var(--senren-primary))] focus:px-4 focus:py-2 focus:text-sm focus:font-medium focus:text-[hsl(var(--senren-primary-foreground))]">
    <%= skip_label %>
  </a>

  <% if top_nav? %>
    <%= top_nav %>
  <% end %>

  <div class="mx-auto grid w-full max-w-[1400px] gap-6 px-4 py-6 md:grid-cols-[16rem_minmax(0,1fr)] lg:px-8">
    <% if sidebar? %>
      <aside class="min-w-0"><%= sidebar %></aside>
    <% end %>

    <main id="<%= content_id %>" class="min-w-0 <%= sidebar? ? '' : 'md:col-span-2' %>">
      <% if header? %>
        <div class="mb-6"><%= header %></div>
      <% end %>
      <%= content %>
    </main>
  </div>

  <% if footer? %>
    <footer class="border-t border-[hsl(var(--senren-border))] px-4 py-6 text-sm text-[hsl(var(--senren-muted-foreground))] lg:px-8">
      <%= footer %>
    </footer>
  <% end %>
<% end %>
app/components/senren/app_shell_component.rb
# frozen_string_literal: true

module Senren
  class AppShellComponent < BaseComponent
    renders_one :top_nav
    renders_one :sidebar
    renders_one :header
    renders_one :footer

    VARIANTS = {
      default: 'bg-[hsl(var(--senren-background))]',
      compact: 'bg-[hsl(var(--senren-muted)/0.25)]'
    }.freeze
    SIZES = { md: '' }.freeze

    def initialize(content_id: 'senren-main', skip_label: 'Skip to content', variant: :default, class_name: nil, **html)
      super(variant: variant, size: :md, class_name: class_name, **html)
      @content_id = content_id
      @skip_label = skip_label
    end

    attr_reader :content_id, :skip_label
  end
end

AI agent rules #

Use for

  • +SaaS dashboard layout root

Avoid

  • -marketing pages

Accessibility #

  • Provide skip-to-content link; main landmark for page body.