App Shell
StableSaaS 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.