Components
Navigation
Navigation uses semantic lists of destinations, wraps on narrow screens, and can include native disclosure dropdowns or clearly separated actions.
Basic navigation
.menu is presentation on native navigation, not role="menu". Links keep normal Tab navigation; no JavaScript is required.
Use one or more lists inside a labelled nav. A label distinguishes this landmark from other navigation regions; aria-current="page" identifies and emphasizes the current destination.
Add .menu to opt into indicators for nav.menu > ul > li > a. These top-level plain links inherit color and weight, preserve explicit .primary, .secondary, and .contrast colors, have no text underline, and use inline-block layout with 0.35rem 0.5rem padding. A transparent --tty-frame-width bottom border reserves space: hover shows a 45% current-color border, while aria-current="page" keeps a solid border, including on hover. Keyboard focus outlines are unchanged.
Without .menu, navigation retains its horizontal layout and ordinary link treatment. Button-styled links (a.button and legacy a[role="button"]) and dropdown items keep their own styling. The opt-in does not depend on ancestors: leave it off sidebars and breadcrumbs.
<nav class="menu" aria-label="Console navigation">
<ul>
<li><a href="#basic"><strong>Console</strong></a></li>
</ul>
<ul>
<li><a href="#basic" aria-current="page">Overview</a></li>
<li><a href="#breadcrumbs">Queries</a></li>
<li><a href="#actions">Settings</a></li>
</ul>
</nav>Pagination
Add .pagination to a labelled nav for links that share the framework’s secondary button styling, including borders, offset shadows, hover, and pressed states, and wrap within their container. No .button class is needed on individual links. Use direct links or a direct ul or ol; numbered pages are optional. Do not add .menu or button roles. URLs, cursor values, page counts, and available destinations belong to your application; tty.css supplies styling only.
Numbered pages
Use aria-current="page" on the current page link or a non-interactive span. The current page uses the primary button fill and pressed appearance, with an underline that identifies it without color. Unavailable spans share the disabled button treatment; ellipses and result summaries stay plain text. Give numeric links descriptive accessible names.
<nav class="pagination" aria-label="Search result pages">
<ul>
<li><a href="?page=1" rel="prev">Previous</a></li>
<li><a href="?page=1" aria-label="Page 1">1</a></li>
<li><a href="?page=2" aria-label="Page 2" aria-current="page">2</a></li>
<li><a href="?page=3" aria-label="Page 3">3</a></li>
<li><span aria-label="More pages">…</span></li>
<li><a href="?page=20" aria-label="Page 20">20</a></li>
<li><a href="?page=3" rel="next">Next</a></li>
</ul>
<small>Showing 21–40 of 400</small>
</nav>Cursor-based pagination
Previous and next links work without page numbers, a current-page marker, or a known total. Use the opaque cursors returned by your application in real URLs. The optional result summary is ordinary text, not an automatic live announcement.
<nav class="pagination" aria-label="Event batches">
<a href="?before=evt_021" rel="prev">Previous</a>
<a href="?after=evt_040" rel="next">Next</a>
<small>Showing 20 events</small>
</nav>Boundaries and sequential content
Omit an unavailable destination, or replace its link with <span aria-disabled="true">. A span has no link behavior and is not in the Tab order. Do not use disabled on links, fake URLs, or aria-disabled alone on a working link. Sequential content can use descriptive labels instead of page numbers.
<nav class="pagination" aria-label="Post archives">
<span aria-disabled="true">Newer posts</span>
<a href="?before=post_040" rel="next">Older posts →</a>
</nav>These previews demonstrate markup only; their query strings do not change the sample results. Real links retain normal Tab and Enter behavior, bookmarking, and opening in another tab. Icon-only links need an accessible name. If your application replaces results without navigation, it also owns focus management and result announcements.
Load more
Appending results is an action, not a destination. Use a regular button outside a pagination navigation landmark, and supply application code to fetch and append results, manage busy and unavailable states, and announce updates. No pagination-specific class is needed.
<button type="button">Load more results</button>Link dropdown
details.dropdown is a select-like disclosure. Outside navigation it fills the available width. Place a ul directly after the summary; no wrapper is needed. Links remain ordinary destinations.
The list scrolls at its maximum height. Dropdown overlays are shadowless and do not support frame treatments.
Choose documentation
<details class="dropdown">
<summary>Choose documentation</summary>
<ul>
<li><a href="#link-dropdown">Quick start</a></li>
<li><a href="#link-dropdown">Components</a></li>
<li><a href="#link-dropdown">Accessibility</a></li>
</ul>
</details>Checkbox dropdown
Menu rows may contain labelled checkboxes for multiple independent choices. Give each control a useful name; clicking a checkbox does not need to close the native disclosure.
Select services
<details class="dropdown">
<summary>Select services</summary>
<ul>
<li><label><input type="checkbox" name="services" value="search" checked> Search</label></li>
<li><label><input type="checkbox" name="services" value="storage"> Storage</label></li>
<li><label><input type="checkbox" name="services" value="events"> Events</label></li>
</ul>
</details>Dropdown button variants
Add role="button" to a dropdown summary for button presentation. The default, .secondary, .contrast, and .outline variants are supported here. The native summary remains the disclosure control.
<div class="grid">
<details class="dropdown">
<summary role="button">Primary</summary>
<ul><li><a href="#dropdown-variants">Profile</a></li></ul>
</details>
<details class="dropdown">
<summary role="button" class="secondary">Secondary</summary>
<ul><li><a href="#dropdown-variants">Profile</a></li></ul>
</details>
<details class="dropdown">
<summary role="button" class="contrast">Contrast</summary>
<ul><li><a href="#dropdown-variants">Profile</a></li></ul>
</details>
<details class="dropdown">
<summary role="button" class="outline">Outline</summary>
<ul><li><a href="#dropdown-variants">Profile</a></li></ul>
</details>
</div>Validation states
Put aria-invalid="true" or aria-invalid="false" on the summary for invalid and explicitly valid borders. Connect explanatory text with aria-describedby; there is no aria-valid attribute.
Region: US East
Choose a region
<div class="grid">
<div>
<details class="dropdown">
<summary aria-invalid="false" aria-describedby="region-valid">Region: US East</summary>
<ul>
<li><a href="#validation">US East</a></li>
<li><a href="#validation">EU Central</a></li>
</ul>
</details>
<small class="helper" id="region-valid">Region is available.</small>
</div>
<div>
<details class="dropdown">
<summary aria-invalid="true" aria-describedby="region-error">Choose a region</summary>
<ul>
<li><a href="#validation">US East</a></li>
<li><a href="#validation">EU Central</a></li>
</ul>
</details>
<small class="helper" id="region-error">A deployment region is required.</small>
</div>
</div>Navigation actions
Buttons may appear in navigation when they perform an action. Destinations remain links, and every button should declare its type.
<nav class="menu" aria-label="Deployment navigation">
<ul>
<li><a href="#actions" aria-current="page">Deployments</a></li>
<li><a href="#actions">Logs</a></li>
</ul>
<ul>
<li><button type="button" class="secondary">Refresh</button></li>
<li><button type="button">New deployment</button></li>
</ul>
</nav>Native and application behavior
The browser makes details and summary operable by keyboard and pointer, exposes expanded state, and toggles the disclosure without JavaScript. These are disclosure lists, not ARIA menus, so do not add role="menu" or menu keyboard behavior to otherwise native markup.
tty.css only provides presentation. Application code is responsible for updating a choice summary, closing after selection when appropriate, closing on outside interaction or Escape if required, running button actions, and keeping validation messages and state synchronized. Preserve native link navigation and checkbox behavior when enhancing a dropdown.