Menu

Change language

GitHub repo

Dashboard Layout

blocklayout

Full dashboard shell — collapsible sidebar + sticky topbar (sidebar trigger, breadcrumb, theme switch, notifications) + main content children. Drop it around a page: `<DashboardLayout>{page}</DashboardLayout>`.

Installation

$npx shadcn@latest add https://uipkge.dev/r/lit/dashboard-layout.json
Manifest:https://uipkge.dev/r/lit/dashboard-layout.jsonInstalls to:src/uip/blocks/dashboard-layout/

Variants

Loading interactive previews…

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

DashboardCrumb
interface DashboardCrumb {
  label: string
  href?: string
}
HTMLElementTagNameMap
interface HTMLElementTagNameMap {
    'uip-dashboard-layout': UipDashboardLayout
  }
}

Files installed (1)

  • src/uip/blocks/dashboard-layout/dashboard-layout.ts6 kB
    import { LitElement, css, html } from 'lit'
    import { Bell, LayoutDashboard, Settings, Users } from 'lucide'
    import { tailwind } from '../../lib/styles'
    import { ThemeController } from '../../lib/theme'
    import { icon } from '../../lib/icon'
    // Side-effect imports: register the composed elements for standalone use
    // (consumers who install only this block still get working primitives).
    import '../../components/breadcrumb/breadcrumb'
    import '../../components/button/button'
    import '../../components/separator/separator'
    import '../../components/sidebar/sidebar'
    import '../../components/theme-switch/theme-switch'
    
    export interface DashboardCrumb {
      label: string
      href?: string
    }
    
    const NAV = [
      { label: 'Dashboard', icon: LayoutDashboard, iconName: 'layout-dashboard' },
      { label: 'People', icon: Users, iconName: 'users' },
      { label: 'Settings', icon: Settings, iconName: 'settings' },
    ]
    
    /**
     * <uip-dashboard-layout> — the registry DashboardLayout block as a web component.
     *
     * Class strings are React's DashboardLayout verbatim: collapsible-icon
     * sidebar + sticky topbar (sidebar trigger, breadcrumb, theme switch,
     * notifications) + main content. Page content goes in the default slot
     * (React's `children`). `breadcrumbs` is a property (no attribute —
     * structured data).
     *
     * React's `user` / `onProfileSelect` / `onCommandSelect` props are accepted
     * but unused in React itself (the sidebar owns the profile affordance), so
     * they are not ported. The notifications bell is a plain button, as in React.
     */
    export class UipDashboardLayout extends LitElement {
      static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }
      // :host display can't be a utility class (the host has no template of its own).
      static styles = [tailwind, css`:host { display: block; }`]
    
      static properties = {
        breadcrumbs: { attribute: false },
      }
    
      breadcrumbs: DashboardCrumb[] = [{ label: 'Dashboard' }]
    
      constructor() {
        super()
        new ThemeController(this)
      }
    
      connectedCallback() {
        super.connectedCallback()
        this.setAttribute('data-uipkge', '')
        this.setAttribute('data-slot', 'dashboard-layout')
      }
    
      render() {
        const crumbs = this.breadcrumbs.length ? this.breadcrumbs : [{ label: 'Dashboard' }]
        return html`
          <uip-sidebar-provider>
            <a
              href="#main-content"
              class="bg-background text-foreground ring-ring sr-only z-50 rounded-md text-sm font-medium shadow-md ring-2 focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:px-3 focus:py-2"
            >
              Skip to content
            </a>
            <uip-sidebar collapsible="icon">
              <uip-sidebar-header></uip-sidebar-header>
              <uip-sidebar-content>
                <uip-sidebar-group>
                  <uip-sidebar-group-content>
                    <uip-sidebar-menu>
                      ${NAV.map(
                        (item) => html`
                          <uip-sidebar-menu-item>
                            <uip-sidebar-menu-button tooltip=${item.label}>
                              ${icon(item.icon, item.iconName)}<span>${item.label}</span>
                            </uip-sidebar-menu-button>
                          </uip-sidebar-menu-item>
                        `,
                      )}
                    </uip-sidebar-menu>
                  </uip-sidebar-group-content>
                </uip-sidebar-group>
              </uip-sidebar-content>
              <uip-sidebar-rail></uip-sidebar-rail>
            </uip-sidebar>
            <uip-sidebar-inset>
              <header
                class="bg-background/80 supports-[backdrop-filter]:bg-background/60 sticky top-0 z-30 flex h-14 w-full shrink-0 items-center justify-between border-b px-4 backdrop-blur-xl transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12"
              >
                <div class="flex items-center gap-2">
                  <uip-sidebar-trigger class="-ml-1"></uip-sidebar-trigger>
                  <uip-separator orientation="vertical" class="[&::part(base)]:mr-2 [&::part(base)]:h-4"></uip-separator>
                  <uip-breadcrumb>
                    ${crumbs.map(
                      (crumb, i) => html`
                        <uip-breadcrumb-item class=${i === 0 ? 'hidden md:block' : ''}>
                          ${crumb.href && i < crumbs.length - 1
                            ? html`<uip-breadcrumb-link
                                href=${crumb.href}
                                class="text-muted-foreground/70 hover:text-foreground transition-colors"
                                >${crumb.label}</uip-breadcrumb-link
                              >`
                            : html`<uip-breadcrumb-page class="font-medium">${crumb.label}</uip-breadcrumb-page>`}
                        </uip-breadcrumb-item>
                        ${i < crumbs.length - 1
                          ? html`<uip-breadcrumb-separator
                              class=${i === 0 ? 'hidden md:block' : ''}
                            ></uip-breadcrumb-separator>`
                          : ''}
                      `,
                    )}
                  </uip-breadcrumb>
                </div>
                <div class="flex items-center gap-1 px-2 sm:gap-3">
                  <div class="flex items-center gap-0.5">
                    <uip-theme-switch variant="icon-only"></uip-theme-switch>
                    <uip-button
                      variant="ghost"
                      size="icon"
                      class="text-muted-foreground hover:text-foreground relative size-8 rounded-lg"
                      aria-label="Notifications"
                    >
                      ${icon(Bell, 'bell', 'size-4')}
                    </uip-button>
                  </div>
                </div>
              </header>
              <main id="main-content" tabindex="-1" class="flex flex-1 flex-col px-4 pt-4 pb-4 outline-none lg:px-6 lg:pt-6 lg:pb-6">
                <slot></slot>
              </main>
            </uip-sidebar-inset>
          </uip-sidebar-provider>
        `
      }
    }
    
    customElements.get('uip-dashboard-layout') || customElements.define('uip-dashboard-layout', UipDashboardLayout)
    
    declare global {
      interface HTMLElementTagNameMap {
        'uip-dashboard-layout': UipDashboardLayout
      }
    }
    

Raw manifest:https://uipkge.dev/r/lit/dashboard-layout.json