Dropdown Menu
Tier 2: Light-DOMAccessible roving tabindex dropdown menu in Light-DOM
Installation
$npx nnui add dropdown
copy$npm install nnui-kit
copyInteractive Preview & Code
My Account
<ui-dropdown>
<button data-part="trigger" data-ui="button" data-variant="outline">
My Account ▾
</button>
<div data-part="menu">
<div data-part="label">My Account</div>
<button data-part="item" data-value="profile">
<span data-part="item-main">Profile</span>
<span data-part="shortcut">⇧⌘P</span>
</button>
<button data-part="item" data-value="billing">
<span data-part="item-main">Billing</span>
<span data-part="shortcut">⌘B</span>
</button>
<button data-part="item" data-value="settings">
<span data-part="item-main">Settings</span>
<span data-part="shortcut">⌘S</span>
</button>
<div data-part="separator"></div>
<button data-part="item" data-value="logout" data-variant="destructive">
<span data-part="item-main">Log out</span>
<span data-part="shortcut">⇧⌘Q</span>
</button>
</div>
</ui-dropdown>
<script>
document.querySelector('ui-dropdown').addEventListener('select', (e) => {
console.log('Selected action:', e.detail.value);
});
</script>import React, { useState } from 'react';
import 'nnui-kit'; // Registers universal Light-DOM elements
export default function DropdownmenuDemo() {
const [tab, setTab] = useState('all');
const [val, setVal] = useState(50);
const [selected, setSelected] = useState('');
const [query, setQuery] = useState('');
return (
<ui-dropdown onSelect={(e: any) => console.log("Selected:", e.detail.value)}>
<button data-part="trigger" data-ui="button" data-variant="outline">
My Account ▾
</button>
<div data-part="menu">
<div data-part="label">My Account</div>
<button data-part="item" data-value="profile">
<span data-part="item-main">Profile</span>
<span data-part="shortcut">⇧⌘P</span>
</button>
<button data-part="item" data-value="billing">
<span data-part="item-main">Billing</span>
<span data-part="shortcut">⌘B</span>
</button>
<button data-part="item" data-value="settings">
<span data-part="item-main">Settings</span>
<span data-part="shortcut">⌘S</span>
</button>
<div data-part="separator"></div>
<button data-part="item" data-value="logout" data-variant="destructive">
<span data-part="item-main">Log out</span>
<span data-part="shortcut">⇧⌘Q</span>
</button>
</div>
</ui-dropdown>
<script>
document.querySelector('ui-dropdown').addEventListener('select', (e) => {
console.log('Selected action:', e.detail.value);
});
</script>
);
}<script setup>
import { ref } from 'vue';
import 'nnui-kit'; // Registers universal Light-DOM elements
const activeTab = ref('all');
const sliderValue = ref(50);
const selected = ref('');
const query = ref('');
const handleSelect = (val) => {
console.log('Selected:', val);
};
</script>
<template>
<ui-dropdown @select="(e) => handleSelect(e.detail.value)">
<button data-part="trigger" data-ui="button" data-variant="outline">
My Account ▾
</button>
<div data-part="menu">
<div data-part="label">My Account</div>
<button data-part="item" data-value="profile">
<span data-part="item-main">Profile</span>
<span data-part="shortcut">⇧⌘P</span>
</button>
<button data-part="item" data-value="billing">
<span data-part="item-main">Billing</span>
<span data-part="shortcut">⌘B</span>
</button>
<button data-part="item" data-value="settings">
<span data-part="item-main">Settings</span>
<span data-part="shortcut">⌘S</span>
</button>
<div data-part="separator"></div>
<button data-part="item" data-value="logout" data-variant="destructive">
<span data-part="item-main">Log out</span>
<span data-part="shortcut">⇧⌘Q</span>
</button>
</div>
</ui-dropdown>
<script>
document.querySelector('ui-dropdown').addEventListener('select', (e) => {
console.log('Selected action:', e.detail.value);
});
</script>
</template><script>
import 'nnui-kit';
let activeTab = $state('all');
let sliderValue = $state(50);
let selected = $state('');
</script>
<ui-dropdown onselect={(e) => console.log(e.detail.value)}>
<button data-part="trigger" data-ui="button" data-variant="outline">
My Account ▾
</button>
<div data-part="menu">
<div data-part="label">My Account</div>
<button data-part="item" data-value="profile">
<span data-part="item-main">Profile</span>
<span data-part="shortcut">⇧⌘P</span>
</button>
<button data-part="item" data-value="billing">
<span data-part="item-main">Billing</span>
<span data-part="shortcut">⌘B</span>
</button>
<button data-part="item" data-value="settings">
<span data-part="item-main">Settings</span>
<span data-part="shortcut">⌘S</span>
</button>
<div data-part="separator"></div>
<button data-part="item" data-value="logout" data-variant="destructive">
<span data-part="item-main">Log out</span>
<span data-part="shortcut">⇧⌘Q</span>
</button>
</div>
</ui-dropdown>
<script>
document.querySelector('ui-dropdown').addEventListener('select', (e) => {
console.log('Selected action:', e.detail.value);
});
</script>How to Utilize
NNUI-KIT components live directly in the document Light DOM. You can use them via declarative HTML, with our first-class React wrapper, or platform-natively in Svelte, Vue, Astro, and HTMX:
React 18 & 19 (Next.js 15 App Router)
import 'nnui-kit/css';
import 'nnui-kit/client';
import { DropdownMenu } from 'nnui-kit/react';Universal Web Components (Vue 3, Svelte 5, Astro, HTMX)
<!-- Automatically registers <ui-dropdown> and <nnui-dropdown> -->
import 'nnui-kit/css';
import 'nnui-kit/client';Platform Features
Zero Shadow DOM
Element lives in the Light DOM. Tailwind and global CSS apply directly.
persist="url | local"
Add declarative persistence attribute to auto-sync state with URL search params.
sync="cross-tab"
Synchronize changes across all open browser tabs via BroadcastChannel.