Profile Dropdown Menu — Account Menu HTML CSS JS

Profile Dropdown Menu · Navigation · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Identity-confirming trigger
Shows the avatar, name, and a caret so the active account is clear at a glance — useful for multi-account users.
Initials gradient avatar
A gradient-and-initials avatar needs no profile image, with a larger version in the menu header.
Rich account header
The menu repeats avatar, full name, and email to confirm the signed-in identity.
Plan badge with quick action
Surfaces subscription status and a direct "Manage" link to billing.
Separated sign-out
A red-tinted, visually divided sign-out keeps the exit action distinct from normal nav links.
Keyboard navigation
Arrow keys move through items (wrapping), and Escape closes the menu and restores focus to the trigger.
Accessible menu semantics
aria-haspopup, toggled aria-expanded, role="menu", and role="menuitem" make it a real menu for assistive tech.
Anchored, animation-safe open
Opacity/transform-only animation from a top-right origin so the menu grows out of the avatar smoothly.

About this UI Snippet

Profile Dropdown Menu — Avatar Account Menu with Keyboard Navigation

Screenshot of the Profile Dropdown Menu snippet rendered live

The avatar in the top-right corner that opens to reveal your account details, settings links, and sign-out is on virtually every app's navbar — and it's the kind of component developers rebuild constantly. This snippet provides a complete, polished profile dropdown in plain HTML, CSS, and vanilla JavaScript: a trigger showing the user's avatar and name, and a rich menu with account info, a plan badge, navigation links, and a distinct sign-out action, all keyboard accessible.

A trigger that shows who's signed in

The trigger isn't just an avatar — it pairs the user's initials-avatar with their name and a caret, so a glance confirms which account is active (important for anyone who switches between work and personal logins). The avatar uses a gradient background with initials, so no profile image is required, and the whole trigger is a pill that subtly highlights on hover and when open. The caret rotates to signal the menu's state.

A menu that's more than links

The dropdown opens with a header repeating the avatar plus the full name and email — the standard confirmation of the signed-in identity — followed by a plan badge ("Pro plan · Manage") that surfaces subscription status and a quick path to billing. Below that sit the navigation links (profile, settings, billing, help), each with an icon, and finally a visually separated, red-tinted "Sign out" — deliberately set apart so the destructive/exit action is never confused with a normal nav link. This information hierarchy (identity → status → navigation → exit) is what users expect from an account menu.

Keyboard accessible by design

The trigger carries aria-haspopup="menu" and a toggled aria-expanded, the menu is role="menu" with role="menuitem" entries, and the script wires the expected keyboard behavior: Escape closes the menu and returns focus to the trigger (so keyboard users aren't stranded), and Arrow Up/Down move focus through the menu items, wrapping at the ends. This is the accessibility most hand-built profile menus skip — a menu you can only operate with a mouse excludes keyboard and screen-reader users from a core navigation control.

Smooth, correctly-anchored animation

The menu animates in with opacity and transform (a slight rise and scale) from a top right transform origin, so it appears to grow out of the avatar in the corner rather than sliding in from nowhere. Using transform and opacity only keeps it smooth and crash-safe across every framework export. It's right-aligned to the trigger so it never overflows the viewport edge on a top-right navbar placement.

Outside-click dismissal and clean state

A document-level click listener closes the menu when you click anywhere outside it, and the trigger toggles open/closed on click — the standard, expected behavior. The component is self-contained: drop it into a navbar, swap the name, email, initials, plan, and links for your real user data, and wire the sign-out button to your auth logout. The structure stays identical whether the user is on a free or paid plan.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You don't have to reverse-engineer every accessibility wire-up by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to walk through exactly how the document-level click listener decides an outside click occurred using wrap.contains(e.target), or why the menu animates with opacity and transform from a top-right transform-origin instead of sliding in from the edge. The same assistant is useful for optimizing it too, for example checking whether attaching a new document click listener is necessary at all versus a single delegated listener, or whether the arrow-key handler's items query should be cached instead of rebuilt on every keydown. It's just as good for extending the menu: ask it to add a submenu for account switching, trap Tab focus inside the menu while open, or animate the caret and avatar together on hover. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a "profile dropdown menu" (the avatar-triggered account menu found in app navbars) in plain HTML, CSS, and JavaScript with no framework and no libraries.

Requirements:
- A trigger button showing an initials avatar, the user's name, and a caret icon, carrying aria-haspopup="menu" and an aria-expanded attribute that toggles between "true" and "false".
- A menu panel with role="menu" containing: a header repeating the avatar (larger) plus full name and email, a plan/status badge with a manage link, a list of navigation links each with role="menuitem", and a visually distinct sign-out control (different color, separated by a divider) as its own role="menuitem".
- The menu must be positioned absolutely relative to the trigger's wrapper, right-aligned so it never overflows a top-right navbar placement, and animate open/closed using only opacity and transform (translateY plus scale) from a top-right transform-origin — no width/height animation, no JavaScript-driven position calculation.
- Clicking the trigger must toggle the menu open state by adding/removing a class on the wrapper (which CSS then keys off to reveal the menu), not by directly toggling inline styles.
- A document-level click listener must close the menu whenever a click lands outside the wrapper element (checked via a contains() call), and a document-level keydown listener must close the menu on Escape and return keyboard focus to the trigger button.
- A keydown listener scoped to the menu must let ArrowDown and ArrowUp move focus between the menu's focusable link and button elements, wrapping around from the last item back to the first and vice versa.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA navbar renders with an avatar + name trigger in the top right. Click it to open the account menu.
  2. 2
    Explore the menuSee the header (avatar, name, email), a plan badge, navigation links with icons, and a separated sign-out.
  3. 3
    Use the keyboardOpen it, then Arrow Up/Down to move between items, and Escape to close and return focus to the trigger.
  4. 4
    Click outside to dismissClicking anywhere outside the menu closes it — the standard dropdown behavior.
  5. 5
    Add your user dataReplace the name, email, initials, plan, and links with your real signed-in user's details.
  6. 6
    Wire up sign-outConnect the "Sign out" button to your auth provider's logout, and point the links at your real routes.

Real-world uses

Common Use Cases

App and dashboard navbars
The standard top-right account menu for any signed-in product — pair with an expandable search and notifications in the bar.
SaaS settings access
Give quick paths to profile, billing, and settings, surfacing the color mode toggle for theme as well.
Admin panels
Show the logged-in admin's identity and a clear sign-out for shared workstations.
E-commerce account menus
Link to orders, addresses, and wishlist from a profile dropdown in the store header.
Multi-account products
Confirm which account is active and offer account switching from the menu.
Learning accessible menus
A reference for menu ARIA, focus return, and arrow-key navigation — compare with a dropdown menu for the generic version.

Got questions?

Frequently Asked Questions

Replace the static name, email, and initials with your user object's values (compute initials from the name, e.g. first letters of the first two words), set the plan badge from their subscription, and point the links at your routes. If you have a profile image, swap the initials avatar for an <img> with the initials div as a fallback when no image is set.

This snippet handles Escape-to-close-with-focus-return and Arrow key navigation. For complete menu semantics, also move focus to the first item when the menu opens, trap Tab within the menu while open, and ensure each item is reachable — the role="menu"/"menuitem" attributes are already in place so screen readers announce it correctly.

Attach a handler to the .pfd-signout button that calls your auth provider's logout (Auth0 logout(), Firebase signOut(), Supabase auth.signOut(), or your own endpoint), then redirect to the login or home page. Keep it as a button (not a link) since it triggers an action rather than navigating to a URL.

Sign-out is a state-changing, session-ending action, so visually grouping it with ordinary navigation links risks accidental clicks. Giving it a divider, distinct red color, and its own position at the bottom follows the established convention that makes the exit action deliberate and unmistakable.

In React, hold the open state in useState, render the user data from props, and handle outside-click with a useEffect document listener cleaned up on unmount; in Vue, use ref() with onMounted/onUnmounted; in Angular, use a component field with HostListener('document:click'). The keyboard and ARIA wiring port directly — only the open state and outside-click move into the framework's lifecycle.