Blocks

Navigation Menu

A horizontal nav menu for site headers. Ports the structural pieces of shadcn/ui's Radix Navigation Menu — no dropdown viewport.

Installation

bin/rails g shadcnrb:component navigation_menu

Example

<%= sui.navigation_menu do |nav| %>
  <%= nav.item { nav.link "Home", root_path } %>
  <%= nav.item { nav.link "Docs", docs_path } %>
  <%= nav.item { nav.link "Blocks", blocks_path } %>
  <%= nav.item { nav.link "GitHub", "https://github.com/accountaim/shadcn-rb", target: "_blank", rel: "noopener" } %>
<% end %>

Item with dropdown (hover)

Pair nav.trigger with nav.content inside a single nav.item. Opens on hover / focus, closes after a short delay when the pointer leaves both the trigger and the content — matching shadcn/ui's NavigationMenuTrigger. Click also toggles for touch.

<%= sui.navigation_menu do |nav| %>
  <%= nav.item { nav.link "Home", root_path } %>
  <%= nav.item do %>
    <%= nav.trigger value: "products" do %>
      Products
      <%= sui.icon :"chevron-down", class: "size-4 opacity-60 transition-transform group-data-[state=open]:rotate-180" %>
    <% end %>
    <%= nav.content value: "products" do %>
      <div class="grid gap-1 w-64">
        <% [
          ["Feature A",    "Fast, reliable, and accessible."],
          ["Feature B",    "Works on every device, out of the box."],
          ["Integrations", "Connect with the tools your team already uses."],
          ["All products", "Explore the full platform."]
        ].each do |(title, desc)| %>
          <%= link_to "#", class: "block rounded-md p-2 hover:bg-accent hover:text-accent-foreground" do %>
            <div class="text-sm font-medium leading-none"><%= title %></div>
            <p class="mt-1 text-xs text-muted-foreground line-clamp-2"><%= desc %></p>
          <% end %>
        <% end %>
      </div>
    <% end %>
  <% end %>
  <%= nav.item { nav.link "Pricing", "#" } %>
  <%= nav.item { nav.link "Docs",    docs_path } %>
<% end %>

With logo + centered items

Logo on the left, nav items centered, CTA on the right — classic marketing header.

<div class="flex w-full items-center gap-6">
  <div class="flex items-center gap-2">
    <div class="flex h-7 w-7 items-center justify-center rounded-md bg-primary text-primary-foreground text-sm font-bold">A</div>
    <span class="font-semibold">Acme</span>
  </div>
  <div class="flex-1 flex justify-center">
    <%= sui.navigation_menu do |nav| %>
      <%= nav.item { nav.link "Features", "#" } %>
      <%= nav.item { nav.link "Pricing",  "#" } %>
      <%= nav.item { nav.link "About",    "#" } %>
    <% end %>
  </div>
  <%= sui.link_to "Get started", "#", variant: :default, size: :sm %>
</div>

With user profile dropdown

App-shell top bar: brand + nav items on the left, account dropdown on the right.

<div class="flex w-full items-center gap-6">
  <span class="font-semibold">Acme</span>
  <%= sui.navigation_menu do |nav| %>
    <%= nav.item { nav.link "Dashboard", "#", active: true } %>
    <%= nav.item { nav.link "Projects",  "#" } %>
    <%= nav.item { nav.link "Team",      "#" } %>
  <% end %>
  <div class="ml-auto">
    <%= sui.dropdown_menu do |m| %>
      <%= m.trigger variant: :ghost, size: :"icon-sm", "aria-label": "Account" do %>
        <%= sui.avatar size: :sm do |a| %>
          <%= a.fallback "SC" %>
        <% end %>
      <% end %>
      <%= m.content class: "w-48" do %>
        <%= m.label "My Account" %>
        <%= m.separator %>
        <%= m.item "Profile",       "#" %>
        <%= m.item "Billing",       "#" %>
        <%= m.item "Team settings", "#" %>
        <%= m.separator %>
        <%= m.item "Log out", "#" %>
      <% end %>
    <% end %>
  </div>
</div>

Branded / colored

Drop a bg-* utility on an outer wrapper and pass a matching class: to each nav.link — the link's default bg-background is overridden by TailwindMerge when you provide your own.

<div class="flex w-full items-center gap-6 rounded-lg bg-primary px-4 py-2">
  <div class="flex items-center gap-2 text-primary-foreground">
    <div class="flex h-7 w-7 items-center justify-center rounded-md bg-primary-foreground text-primary text-sm font-bold">A</div>
    <span class="font-semibold">Acme</span>
  </div>
  <%= sui.navigation_menu do |nav| %>
    <%= nav.item do %>
      <%= nav.trigger value: "brand-product", class: branded_trigger do %>
        Product
        <%= sui.icon :"chevron-down", class: "size-4 opacity-70 transition-transform group-data-[state=open]:rotate-180" %>
      <% end %>
      <%= nav.content value: "brand-product" do %>
        <div class="grid gap-1 w-64">
          <% [
            ["Overview",     "Everything Acme can do."],
            ["Features",     "Individual capabilities in depth."],
            ["Integrations", "Connect with the tools your team uses."]
          ].each do |(title, desc)| %>
            <%= link_to "#", class: "block rounded-md p-2 hover:bg-accent hover:text-accent-foreground" do %>
              <div class="text-sm font-medium leading-none"><%= title %></div>
              <p class="mt-1 text-xs text-muted-foreground line-clamp-2"><%= desc %></p>
            <% end %>
          <% end %>
        </div>
      <% end %>
    <% end %>
    <%= nav.item { nav.link "Pricing", "#", class: branded_link } %>
    <%= nav.item { nav.link "Docs",    "#", class: branded_link } %>
  <% end %>
  <%= sui.link_to "Sign in", "#", variant: :secondary, size: :sm, class: "ml-auto" %>
</div>

Active state

Pass active: true explicitly or let it auto-detect via current_page? when you pass a Rails path. The marketing header on this site uses auto-detection — whichever page you're on gets an accent background.

Composing with dropdown_menu

Need a dropdown under a nav item? Nest a dropdown_menu inside navigation_menu_item. The wrapper is structural — it doesn't assume a specific trigger type.

Composition — Rails helpers inside

The shortcut form renders an anchor directly. Pass a block to get a styled wrapper and put link_to / button_to inside with full Rails options.

sui.navigation_menu do |nav|
  nav.item do
    nav.link active: current_page?(docs_path) do
      link_to "Docs", docs_path, data: { turbo_frame: "_top" }
    end
  end
end

API

MethodArgsDescription
sui.navigation_menu**optsRoot <nav> + <ul> wrapper; yields a nav proxy
nav.item**optsSingle <li> in the list
nav.linkname, options, active:, **optsLink inside an item. Auto-active via current_page?