Senren UI
Components / Top Nav

Top Nav

Stable

global header with brand and account menu

Senren

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/.../top_nav_example.html.erb
<div class="w-full max-w-2xl">
  <%= render Senren::TopNavComponent.new(
    items: [["Docs", "/docs"], ["Components", "/components"], ["Examples", "/examples"]],
    current_path: "/components"
  ) do |nav| %>
    <% nav.with_brand { "Senren" } %>
    <% nav.with_actions do %>
      <%= render Senren::SearchInputComponent.new(name: "nav_search", placeholder: "Search...") %>
    <% end %>
  <% 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 top_nav

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 top_nav --no-client

Dependencies are resolved by senren:add: link, button, dropdown_menu.

At a glance #

Category Saas
Class name Senren::TopNavComponent
Stimulus
Variants default
Depends on link, button, dropdown_menu
Pairs with app_shell, sidebar

Source #

app/components/senren/top_nav_component.html.erb
<header <%= tag.attributes(**root_attrs("sticky top-0 z-30 border-b border-[hsl(var(--senren-border))] backdrop-blur-md")) %>>
  <div class="mx-auto flex h-14 w-full max-w-[1400px] items-center gap-6 px-4 lg:px-8">
    <div class="min-w-0 flex-none font-display text-sm font-semibold text-[hsl(var(--senren-foreground))]">
      <% if brand? %><%= brand %><% else %>Senren<% end %>
    </div>

    <% if items.any? %>
      <nav aria-label="<%= label %>" class="hidden min-w-0 items-center gap-1 md:flex">
        <% items.each do |item| %>
          <%= link_to item[:label], item[:href], class: "rounded-(--senren-radius) px-3 py-2 text-sm font-medium transition-colors #{active_item?(item) ? 'bg-[hsl(var(--senren-accent))] text-[hsl(var(--senren-accent-foreground))]' : 'text-[hsl(var(--senren-muted-foreground))] hover:bg-[hsl(var(--senren-accent))] hover:text-[hsl(var(--senren-accent-foreground))]'}" %>
        <% end %>
      </nav>
    <% elsif content? %>
      <div class="min-w-0 flex-1"><%= content %></div>
    <% end %>

    <% if actions? %>
      <div class="ml-auto flex items-center gap-2"><%= actions %></div>
    <% end %>
  </div>
</header>
app/components/senren/top_nav_component.rb
# frozen_string_literal: true

module Senren
  class TopNavComponent < BaseComponent
    renders_one :brand
    renders_one :actions

    VARIANTS = {
      default: 'bg-[hsl(var(--senren-background))/0.88]',
      solid: 'bg-[hsl(var(--senren-card))]'
    }.freeze
    SIZES = { md: '' }.freeze

    def initialize(items: [], current_path: nil, label: 'Global', variant: :default, class_name: nil, **html)
      super(variant: variant, size: :md, class_name: class_name, **html)
      @items = normalize_items(items)
      @current_path = current_path
      @label = label
    end

    attr_reader :items, :current_path, :label

    def active_item?(item)
      item[:active] || (current_path && item[:href] == current_path)
    end

    private

    def normalize_items(items)
      Array(items).map do |item|
        if item.is_a?(Hash)
          {
            label: item[:label] || item['label'],
            href: item[:href] || item['href'] || '#',
            active: item[:active] || item['active']
          }
        else
          label, href = item
          { label: label, href: href || '#', active: false }
        end
      end
    end
  end
end

AI agent rules #

Use for

  • +global header with brand and account menu

Avoid

  • -primary in-app navigation - use sidebar

Accessibility #

  • role=banner; nav landmark with label.