Header — Floating Pill
blockmarketingDetached pill navbar that floats above the page with a blurred surface, a sliding active indicator that tracks the current link, and a compact CTA on the right.
Installation
$pnpm dlx uipkge-ng@latest add header-floating-pill$npx uipkge-ng@latest add header-floating-pill$yarn dlx uipkge-ng@latest add header-floating-pill$bunx uipkge-ng@latest add header-floating-pillManifest:
https://uipkge.dev/r/angular/header-floating-pill.jsonInstalls to:src/app/components/blocks/header-floating-pill/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
FloatingPillLinkinterface FloatingPillLink {
label: string
href: string
}PillIndicatorinterface PillIndicator {
left: number
width: number
}npm dependencies
Files installed (2)
src/app/components/blocks/header-floating-pill/header-floating-pill.component.ts5.5 kB
// Detached pill navbar floating above the page with a blurred surface, a sliding // active indicator tracking the current link, and a compact CTA on the right. // Port of the Vue/React block 1:1 — the indicator is measured from the live element // so it fits any label length or font, with no hard-coded widths to drift. import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, Input, OnDestroy, ViewChild, signal, } from '@angular/core' import { Layers, LucideAngularModule, Menu } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiButtonComponent } from '@/ui/button/button.component' import { UiSheetCloseComponent, UiSheetComponent, UiSheetContentComponent, UiSheetHeaderComponent, UiSheetTitleComponent, } from '@/ui/sheet/sheet.component' export interface FloatingPillLink { label: string href: string } export interface PillIndicator { left: number width: number } const links: FloatingPillLink[] = [ { label: 'Product', href: '#product' }, { label: 'Pricing', href: '#pricing' }, { label: 'Customers', href: '#customers' }, { label: 'Docs', href: '#docs' }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-header-floating-pill, [ui-header-floating-pill]', standalone: true, // The block renders its own root <div>: keep this wrapper out of the layout. host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiButtonComponent, UiSheetCloseComponent, UiSheetComponent, UiSheetContentComponent, UiSheetHeaderComponent, UiSheetTitleComponent, ], template: ` <div data-slot="header-floating-pill" [class]="rootClass"> <header class="border-border bg-card/85 pointer-events-auto mx-auto flex h-14 max-w-4xl items-center gap-3 rounded-full border px-3 shadow-sm backdrop-blur" > <a href="#top" class="flex shrink-0 items-center gap-2 pl-2 font-semibold tracking-tight"> <lucide-icon [img]="Layers" class="text-primary size-4" /> Verity </a> <nav #navEl class="relative hidden items-center md:flex" aria-label="Primary"> <span class="bg-muted absolute inset-y-1 rounded-full transition-colors duration-200 ease-out" [style.left.px]="indicator().left" [style.width.px]="indicator().width" aria-hidden="true" ></span> @for (link of pillLinks; track link.href; let i = $index) { <a [href]="link.href" data-pill-link class="focus-visible:ring-ring relative rounded-full px-3.5 py-1.5 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none" [class]="i === active() ? 'text-foreground' : 'text-muted-foreground hover:text-foreground'" [attr.aria-current]="i === active() ? 'page' : null" (mouseenter)="measure(i)" (mouseleave)="measure()" (focus)="measure(i)" (blur)="measure()" (click)="setActive(i)" > {{ link.label }} </a> } </nav> <button ui-button size="sm" class="ml-auto rounded-full">Start free</button> <div ui-sheet [open]="mobileOpen()" (openChange)="mobileOpen.set($event)"> <button ui-button variant="ghost" size="icon" class="rounded-full md:hidden" aria-label="Open navigation menu" (click)="mobileOpen.set(true)" > <lucide-icon [img]="Menu" class="size-5" /> </button> <div ui-sheet-content side="top"> <div ui-sheet-header> <h2 ui-sheet-title>Menu</h2> </div> <nav class="mt-6 grid gap-1" aria-label="Mobile"> @for (link of pillLinks; track link.href; let i = $index) { <a [href]="link.href" class="hover:bg-muted rounded-md px-3 py-2 text-sm font-medium transition-colors" (click)="selectMobile(i)" > {{ link.label }} </a> } </nav> </div> </div> </header> </div> `, }) export class UiHeaderFloatingPillComponent implements AfterViewInit, OnDestroy { protected readonly Layers = Layers protected readonly Menu = Menu protected readonly pillLinks = links @ViewChild('navEl') navEl?: ElementRef<HTMLElement> @Input('class') className?: string readonly active = signal(0) readonly mobileOpen = signal(false) readonly indicator = signal<PillIndicator>({ left: 0, width: 0 }) private observer?: ResizeObserver ngAfterViewInit(): void { this.measure() const nav = this.navEl?.nativeElement if (typeof ResizeObserver !== 'undefined' && nav) { this.observer = new ResizeObserver(() => this.measure()) this.observer.observe(nav) } } ngOnDestroy(): void { this.observer?.disconnect() this.observer = undefined } setActive(index: number): void { this.active.set(index) } selectMobile(index: number): void { this.active.set(index) this.mobileOpen.set(false) } measure(index = this.active()): void { const items = this.navEl?.nativeElement.querySelectorAll<HTMLElement>('[data-pill-link]') const el = items?.[index] if (!el) return this.indicator.set({ left: el.offsetLeft, width: el.offsetWidth }) } get rootClass(): string { return cn('pointer-events-none sticky top-0 z-40 px-4 pt-4', this.className) } }src/app/components/blocks/header-floating-pill/index.ts0.1 kB
export { UiHeaderFloatingPillComponent, type FloatingPillLink, type PillIndicator, } from './header-floating-pill.component'
Raw manifest:https://uipkge.dev/r/angular/header-floating-pill.json