Theme
Sound
Jump to a component
Core · Navigation
TopNav
TopNav is the desktop header: the AniCanon™ mark and wordmark, the main sections as text links, and search and account on the right.
@anicanon/sakuga/uiv1.0Used onWeb
Live, from @anicanon/sakuga 2.0.0
Sakuga for iOS and Android is coming
iOS
Web only
TopNav is made for the web, and isn’t part of Sakuga for iOS yet.
Android
Web only
TopNav is made for the web, and isn’t part of Sakuga for Android yet.
- Use it for
- The desktop header: the AniCanon™ mark, up to five main sections (Home, Series, Shorts, Manga, Creators), search and the account.
- Not for
- Navigation on a phoneTabBar
- The creator's workspace sectionsSideNav
- The views inside a productionProductionNav
- The top bar of a full-screen flowFlowHeader
Guidelines
- Five links at most: Home, Series, Shorts, Manga, Creators. Mark the current one with
current; it getsaria-current="page"and thetopnav-indicatorunderline, which slides to a newly current link onmotion-base. - It sits on the page or over the hero's top scrim, never in a filled bar.
- Phones never show TopNav; they use TabBar.
actions(a prop, or children withslot="actions") sit before search and the account: the notifications bell, anIconButtonwithattentionwhile something is unread.
onSearchruns when Search is pressed andaccount.onClickwhen the account is;search={false}drops Search.searchas an object searches in the bar: SearchField's props plusopen,onOpenandonClose. Search callsonOpen; whileopen, the field takes the links' place, with Close search after it. Escape on an empty field callsonClose, and focus goes back to Search.
import { useState } from 'react';
import { IconButton, TopNav } from '@anicanon/sakuga/ui';
export default function TopNavExample() {
const [value, setValue] = useState('Series');
return (
<TopNav
brand={{ logo: '/art/anicanon-mark.png', name: 'AniCanon' }}
links={['Home', 'Series', 'Shorts', 'Manga', 'Creators'].map((l) => ({
label: l,
current: value === l,
onClick: (e) => {
e.preventDefault();
setValue(l);
},
}))}
actions={[
<IconButton key="s" icon="search" label="Search" variant="plain" />,
<img
key="a"
src="/art/eba-cast-lisa.webp"
alt="Your account"
style={{
width: 34,
height: 34,
borderRadius: '50%',
objectFit: 'cover',
objectPosition: 'top',
}}
/>,
]}
/>
);
}PropsTopNavProps
Prop
Type
What it does
linksrequiredNavLink[]brand{ logo?: string; name?: string; href?: string; }actionsReactNodeOr children with slot="actions": they sit before search and the account.
labelstringDefault 'Main'classNamestringaccount{ name: string; image?: string; onClick?: MouseEventHandler; }The account button; onClick runs when it is pressed.
create{ label?: string; href?: string; onClick?: MouseEventHandler; }With href the Create button is a link; without, a button that runs onClick.
searchboolean | (SearchFieldProps & { open?: boolean; onOpen?: () => void; })false drops the Search button, which shows with an account or create. An object holds the bar's SearchField: Search calls onOpen, and while open the field takes the links' place with Close search (onClose) after it.
onSearchMouseEventHandlerRuns when Search is pressed.
childrenReactNodeTypes
interface NavLink { label: string; href?: string; current?: boolean; onClick?: MouseEventHandler; }
interface SearchFieldProps { /** Names the search and its field; 'Search' when left out. Not drawn. */ label?: string; /** The starting words; the field follows when it changes. */ value?: string; placeholder?: string; /** The words as they are typed. */ onChange?: (text: string) => void; /** Enter with no result active. */ onSubmit?: (text: string) => void; /** Picker's groups; leave out for a search that only submits. */ results?: { title?: string; options: PickerOption[]; }[]; onSelect?: (option: PickerOption) => void; busy?: boolean; /** Said while busy; 'Searching' when left out. */ busyText?: string; /** Said when results has no options; 'Nothing matches' when left out. */ emptyText?: string; onClear?: () => void; /** Escape with nothing left to close or clear. */ onClose?: () => void; /** Starts the results open or closed. */ open?: boolean; autoFocus?: boolean; maxWidth?: number; id?: string; className?: string; }
Each token reads a role, and the role lands on a value in each theme. Change the role and every component that reads it follows.
Colour
Token
Light
Dark
topnav-link-fgTopNav: idle links.text-secondary→neutral-800#2e2d36text-secondary→neutral-200#dcdcdetopnav-link-active-fgTopNav: the current link.text-primary→neutral-925#0b0a10text-primary→neutral-0#fffffftopnav-indicatorTopNav: underline under the current link.accent→camellia-600#c2335faccent→camellia-400#f4779ftopnav-brand-fgTopNav: the AniCanon™ wordmark.text-primary→neutral-925#0b0a10text-primary→neutral-0#ffffffSpace and shape
Token
Reads
Value
topnav-gapgap-3xl40px
topnav-heightits own value
80px
topnav-brand-gapgap-md8px
topnav-brand-img-sizeits own value
28px
topnav-links-gapgap-2xl24px
topnav-links-a-padding-topinset-sm8px
topnav-links-a-padding-bottominset-xs4px
topnav-links-a-page-border-bottom-widthline-strong2px
topnav-actions-gapgap-xl16px
topnav-account-sizeits own value
36px
topnav-account-ring-widthline-strong2px
topnav-account-outer-ring-widthring-outercalc(2px + 2px)
topnav-account-active-scalescale-press-sm.94
topnav-links-glide-border-bottom-widthline-strong2px
topnav-forced-current-widthline-strong2px
Type
Token
Reads
Value
topnav-font-familyfont-sansFunnel Sans
topnav-brand-font-familyfont-displayFunnel Display
topnav-brand-font-weighttext-title-3-weight700
topnav-brand-font-sizetext-title-3-size20px
Aa
topnav-links-a-font-sizetext-body-lg-size15px
Aa
topnav-links-a-font-weighttext-body-lg-weight400
topnav-account-fonttext-caption-strong600 13px/18px "Funnel Sans", system-ui, -apple-system, "Segoe UI", sans-serif
Motion
Token
Reads
Value
topnav-links-a-durationmotion-fast160ms
topnav-links-a-easingease-standardcubic-bezier(0.2, 0, 0, 1)
topnav-account-durationmotion-fast160ms
topnav-account-easingease-standardcubic-bezier(0.2, 0, 0, 1)
topnav-account-active-durationmotion-instant100ms
Checked automatically with axe-core 4.11.4 against WCAG 2.2 A and AA, on the live preview above, in light and dark, on 26 October 2026.
- Result
- No issues found, in light or in dark.
- By design
- A header whose brand link reads the name (the logo is hidden; without
brand.hrefthe brand is the name alone, not a link), then a navigation named bylabel(default "Main") whose links mark the current page (a link withouthrefis a button running itsonClick, never a link to #); Tab moves through them, then theactions, then, when there is an account or Create, "Search" (runningonSearch, orsearch.onOpen; while searching, the search landmark and "Close search" take the links' place and Search is gone), then Create and the account button ("Your account, Aiko Mori", runningaccount.onClick). - Not tested
- The automated check doesn't test keyboard use, focus order or what a screen reader says. The lines above are the design system's own account of them.