All components
Navbar
Top navigation bars in neutral, solid, and glass variants with brand and link slots.
123456789101112131415161718192021import { Navbar } from "#components/ui/base-ui/navbar.tsx";
export function Example() { return ( // variant/size resolve once on Root; every part inherits them via // context — the solid variant restyles the links automatically <Navbar.Root variant="neutral" size="md"> <Navbar.Content> {/* composition: render swaps the anchor for your router's link */} <Navbar.Brand href="/">Acme</Navbar.Brand> <Navbar.ItemGroup> <Navbar.Link href="/dashboard">Dashboard</Navbar.Link> <Navbar.Link href="/members">Members</Navbar.Link> </Navbar.ItemGroup> </Navbar.Content> </Navbar.Root> );}
// bar height comes from --navbar-height (3.5rem by default) — override it// with a StyleX style on RootVariants
Sizes
md
Install
1npx stylexblocks add navbarSource
components/ui/base-ui/navbar.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202import * as stylex from "@stylexjs/stylex";import { createSlotRecipeContext } from "#lib/base-ui/create-slot-recipe-context.tsx";import { defineSlotRecipe } from "#lib/recipe-props.ts";import { colorPalette } from '@stylexblocks/ui/color-palette.stylex.ts'import { durations } from '@stylexblocks/ui/tokens/durations.stylex.ts'import { fontSizes } from '@stylexblocks/ui/tokens/font-sizes.stylex.ts'import { fontWeights } from '@stylexblocks/ui/tokens/font-weights.stylex.ts'import { semanticColors } from '@stylexblocks/ui/semantic-tokens/colors.stylex.ts'import { semanticRadii } from '@stylexblocks/ui/semantic-tokens/radii.stylex.ts'import { semanticShadows } from '@stylexblocks/ui/semantic-tokens/shadows.stylex.ts'import { sizes } from '@stylexblocks/ui/tokens/sizes.stylex.ts'import { spacing } from '@stylexblocks/ui/tokens/spacing.stylex.ts'import { zIndices } from '@stylexblocks/ui/tokens/z-indices.stylex.ts'
const navbarRoot = stylex.create({ base: { display: 'flex', zIndex: zIndices.layer3, width: sizes.full, height: 'auto', alignItems: 'center', justifyContent: 'center', top: 0, transitionProperty: 'background-color, border-color, color, fill, stroke, opacity, box-shadow, transform', transitionDuration: durations.moderate, transitionTimingFunction: 'ease-in-out', },})
const navbarRootSizes = stylex.create({ md: { fontSize: fontSizes.sm, },})
const navbarRootVariants = stylex.create({ neutral: { backgroundColor: semanticColors.bgPanel, color: semanticColors.fg, }, solid: { backgroundColor: colorPalette.solid, color: colorPalette.contrast, }, glass: { backgroundColor: semanticColors.bgOverlay, backdropFilter: 'var(--overlay-effect)', color: semanticColors.fg, },})
const navbarContent = stylex.create({ base: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: sizes.full, height: 'var(--navbar-height)', flexWrap: 'nowrap', },})
const navbarContentSizes = stylex.create({ md: { gap: spacing._4, },})
const navbarBrand = stylex.create({ base: { display: 'flex', alignItems: 'center', justifyContent: 'flex-start', height: sizes.full, backgroundColor: 'transparent', textDecoration: 'none', color: 'inherit', whiteSpace: 'nowrap', boxSizing: 'border-box', },})
const navbarItemGroup = stylex.create({ base: { display: 'flex', alignItems: 'center', justifyContent: 'flex-start', height: sizes.full, backgroundColor: 'transparent', },})
const navbarLink = stylex.create({ base: { backgroundColor: { default: 'transparent', ':hover': semanticColors.bgMuted, }, color: 'currentColor', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', textDecoration: { default: 'none', ':hover': 'none', }, whiteSpace: 'nowrap', boxSizing: 'border-box', borderRadius: semanticRadii.control, transitionProperty: 'background-color, border-color, color, fill, stroke, opacity, box-shadow, transform', transitionDuration: durations.moderate, lineHeight: 1, outline: { default: null, ':focus-visible': 'none', }, boxShadow: { default: null, ':focus-visible': `0 0 0 2px ${colorPalette.focusRing}`, }, fontWeight: { default: null, ':active': fontWeights.semibold, }, },})
const navbarLinkSizes = stylex.create({ md: { paddingInline: spacing._3, height: sizes._8, },})
const navbarLinkVariants = stylex.create({ solid: { backgroundColor: { default: 'transparent', ':hover': `color-mix(in oklch, ${colorPalette.contrast} 10%, transparent)`, }, },})
export type NavbarVariant = keyof typeof navbarRootVariants
export type NavbarSize = keyof typeof navbarRootSizes
// The content row sizes itself from `--navbar-height`; the component supplies// the default so it stands on its own (override it via the `style` prop).const navbarStatic = stylex.create({ height: { "--navbar-height": "3.5rem" },});
// defineSlotRecipe validates the shape at compile time: default values must// exist in the axis maps, and slot keys must be `styles` or a declared axisconst navbarSlotRecipe = defineSlotRecipe({ slots: { root: { styles: navbarRoot, sizes: navbarRootSizes, variants: navbarRootVariants, }, content: { styles: navbarContent, sizes: navbarContentSizes }, brand: { styles: navbarBrand }, itemGroup: { styles: navbarItemGroup }, // the link's variant map is sparse (only `solid` restyles it) — the // mechanical resolver skips axis values a slot doesn't style link: { styles: navbarLink, sizes: navbarLinkSizes, variants: navbarLinkVariants, }, }, defaultVariants: { variant: "neutral", size: "md" },});
/** * Navbar — recipe and component colocated in one file. `<Navbar.Root variant * size>` resolves the recipe once and every part inherits it through context; * each part composes via `render`. */const { withProvider, withContext } = createSlotRecipeContext(navbarSlotRecipe);
export const Navbar = { Root: withProvider( "header", (v) => [ navbarRoot.base, navbarRootSizes[v.size], navbarRootVariants[v.variant], navbarStatic.height, ], "NavbarRoot", ), Content: withContext("div", "content", "NavbarContent"), Brand: withContext("a", "brand", "NavbarBrand"), ItemGroup: withContext("nav", "itemGroup", "NavbarItemGroup"), Link: withContext("a", "link", "NavbarLink"),};