$ cat docs/context-menu.md
updated Recently · 22 min read · published
Context Menu ◆ HTML◆ CSS◆ Tailwind◆ Bootstrap◆ UI◆ Intermediate🎯 Free Tools Introduction
Context menus appear on right-click or long-press, surfacing actions that relate to the element under the cursor. A production-ready menu must handle viewport-aware positioning, keyboard triggers (Shift+F10, Menu key), nested submenus, focus trapping, and screen-reader semantics.
This guide shows the same menu implemented in plain HTML/CSS, Tailwind CSS, and Bootstrap, so you can drop the version that matches your stack.
ℹ info
Always call e.preventDefault() in the contextmenu handler. Otherwise the browser menu will appear on top of your custom menu.
Icons & Keyboard Shortcuts
Inline SVG icons make scan speed faster, and shortcut hints teach power users the equivalent keystrokes. Keep icons monochromatic so they inherit hover color changes.
icons HTML CSS JS Tailwind Bootstrap Live
Copy 1 <div class="demo-area" data-contextmenu="icons"> 2 <span class="trigger-hint"> 3 Right-click for actions 4 </span> 5 <div class="context-menu" role="menu" aria-label="Editor actions"> 6 <button class="menu-item" role="menuitem"> 7 <span class="menu-row"> 8 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"> 9 <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/> 10 <rect x="8" y="2" width="8" height="4" rx="1" ry="1"/> 11 </svg> 12 Copy 13 </span> 14 <kbd class="shortcut"> 15 Ctrl+C 16 </kbd> 17 </button> 18 <button class="menu-item" role="menuitem"> 19 <span class="menu-row"> 20 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"> 21 <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/> 22 <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/> 23 </svg> 24 Paste 25 </span> 26 <kbd class="shortcut"> 27 Ctrl+V 28 </kbd> 29 </button> 30 <button class="menu-item" role="menuitem" disabled> 31 <span class="menu-row"> 32 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"> 33 <path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/> 34 </svg> 35 Delete 36 </span> 37 </button> 38 </div> 39 </div>
Copy 1 .demo-area { 2 position:relative; 3 min-height:180px; 4 display:flex; 5 align-items:center; 6 justify-content:center; 7 background:#0A0A0A; 8 border:1px dashed #2A2A3E; 9 border-radius:8px; 10 font-family:system-ui, 11 sans-serif; 12 overflow:hidden 13 } 14 .trigger-hint { 15 color:#808080; 16 font-size:12px; 17 pointer-events:none 18 } 19 .context-menu { 20 position:fixed; 21 display:none; 22 min-width:200px; 23 background:#151515; 24 border:1px solid #2A2A3E; 25 border-radius:8px; 26 box-shadow:0 10px 30px rgba(0, 27 0, 28 0, 29 .5); 30 padding:6px 0; 31 z-index:50 32 } 33 .context-menu.open { 34 display:block 35 } 36 .menu-item { 37 width:100%; 38 padding:8px 14px; 39 border:none; 40 background:transparent; 41 color:#E0E0E0; 42 font-size:13px; 43 text-align:left; 44 cursor:pointer; 45 display:flex; 46 align-items:center; 47 justify-content:space-between; 48 gap:12px 49 } 50 .menu-item:hover:not(:disabled) { 51 background:#1A1A2E; 52 color:#00FF41 53 } 54 .menu-item:focus { 55 outline:none; 56 background:#1A1A2E; 57 color:#00FF41 58 } 59 .menu-item:disabled { 60 opacity:.4; 61 cursor:not-allowed 62 } 63 .menu-row { 64 display:flex; 65 align-items:center; 66 gap:8px 67 } 68 .shortcut { 69 font-family:ui-monospace, 70 SFMono-Regular, 71 Menlo, 72 monospace; 73 font-size:10px; 74 color:#525252; 75 background:#0A0A0A; 76 padding:2px 6px; 77 border-radius:4px; 78 border:1px solid #2A2A3E 79 }
untitled.js wrap JavaScript
Copy 1 document.querySelectorAll('[data-contextmenu="icons"]').forEach(area=>{ 2 const menu=area.querySelector('.context-menu'); 3 const open=(x,y)=>{menu.style.left=x+'px';menu.style.top=y+'px';menu.classList.add('open');}; 4 area.addEventListener('contextmenu',e=>{e.preventDefault();open(e.clientX,e.clientY);}); 5 document.addEventListener('click',e=>{if(!menu.contains(e.target))menu.classList.remove('open');}); 6 document.addEventListener('keydown',e=>{if(e.key==='Escape')menu.classList.remove('open');}); 7 });
Copy 1 <div data-contextmenu="icons" class="relative min-h-[180px] flex items-center justify-center bg-[#0A0A0A] border border-dashed border-[#2A2A3E] rounded-lg overflow-hidden"> 2 <span class="text-xs text-[#808080] pointer-events-none"> 3 Right-click for actions 4 </span> 5 <div class="context-menu hidden fixed min-w-[200px] bg-[#151515] border border-[#2A2A3E] rounded-lg shadow-2xl py-1.5 z-50" role="menu" aria-label="Editor actions"> 6 <button class="w-full px-3.5 py-2 text-left text-[13px] text-[#E0E0E0] hover:bg-[#1A1A2E] hover:text-[#00FF41] focus:outline-none focus:bg-[#1A1A2E] focus:text-[#00FF41] disabled:opacity-40 disabled:cursor-not-allowed flex items-center justify-between gap-3" role="menuitem"> 7 <span class="inline-flex items-center gap-2"> 8 <svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 9 <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/> 10 <rect x="8" y="2" width="8" height="4" rx="1" ry="1"/> 11 </svg> 12 Copy 13 </span> 14 <kbd class="font-mono text-[10px] text-[#525252] bg-[#0A0A0A] px-1.5 py-0.5 rounded border border-[#2A2A3E]"> 15 Ctrl+C 16 </kbd> 17 </button> 18 <button class="w-full px-3.5 py-2 text-left text-[13px] text-[#E0E0E0] hover:bg-[#1A1A2E] hover:text-[#00FF41] focus:outline-none focus:bg-[#1A1A2E] focus:text-[#00FF41] disabled:opacity-40 disabled:cursor-not-allowed flex items-center justify-between gap-3" role="menuitem"> 19 <span class="inline-flex items-center gap-2"> 20 <svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 21 <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/> 22 <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/> 23 </svg> 24 Paste 25 </span> 26 <kbd class="font-mono text-[10px] text-[#525252] bg-[#0A0A0A] px-1.5 py-0.5 rounded border border-[#2A2A3E]"> 27 Ctrl+V 28 </kbd> 29 </button> 30 <button disabled class="w-full px-3.5 py-2 text-left text-[13px] text-[#E0E0E0] hover:bg-[#1A1A2E] hover:text-[#00FF41] focus:outline-none focus:bg-[#1A1A2E] focus:text-[#00FF41] disabled:opacity-40 disabled:cursor-not-allowed flex items-center justify-between gap-3" role="menuitem"> 31 <span class="inline-flex items-center gap-2"> 32 <svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 33 <path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/> 34 </svg> 35 Delete 36 </span> 37 </button> 38 </div> 39 </div>
Copy 1 <div data-contextmenu="icons" class="position-relative d-flex align-items-center justify-content-center border border-secondary border-dashed rounded overflow-hidden" style="min-height:180px;background:#0A0A0A"> 2 <span class="text-secondary small user-select-none"> 3 Right-click for actions 4 </span> 5 <div class="context-menu dropdown-menu dropdown-menu-dark p-1 shadow" role="menu" aria-label="Editor actions" style="--bs-dropdown-bg:#151515;--bs-dropdown-border-color:#2A2A3E;--bs-dropdown-link-color:#E0E0E0;--bs-dropdown-link-hover-bg:#1A1A2E;--bs-dropdown-link-hover-color:#00FF41;--bs-dropdown-link-active-bg:#00FF41;--bs-dropdown-link-active-color:#0A0A0A;display:none;position:fixed;min-width:200px;"> 6 <button class="dropdown-item d-flex align-items-center justify-content-between gap-2" role="menuitem"> 7 <span class="d-inline-flex align-items-center gap-2"> 8 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 9 <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/> 10 <rect x="8" y="2" width="8" height="4" rx="1" ry="1"/> 11 </svg> 12 Copy 13 </span> 14 <kbd class="small" style="background:#0A0A0A;border:1px solid #2A2A3E;color:#525252;padding:2px 6px;border-radius:4px"> 15 Ctrl+C 16 </kbd> 17 </button> 18 <button class="dropdown-item d-flex align-items-center justify-content-between gap-2" role="menuitem"> 19 <span class="d-inline-flex align-items-center gap-2"> 20 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 21 <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/> 22 <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/> 23 </svg> 24 Paste 25 </span> 26 <kbd class="small" style="background:#0A0A0A;border:1px solid #2A2A3E;color:#525252;padding:2px 6px;border-radius:4px"> 27 Ctrl+V 28 </kbd> 29 </button> 30 <button class="dropdown-item disabled" role="menuitem" disabled> 31 <span class="d-inline-flex align-items-center gap-2"> 32 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 33 <path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/> 34 </svg> 35 Delete 36 </span> 37 </button> 38 </div> 39 </div>
preview
Danger Actions
Destructive items should be visually distinct — usually red text on hover, separated by a divider, and often requiring a confirmation step.
danger HTML CSS JS Tailwind Bootstrap Live
Copy 1 <div class="demo-area" data-contextmenu="danger"> 2 <span class="trigger-hint"> 3 Right-click on this file 4 </span> 5 <div class="context-menu" role="menu" aria-label="File actions"> 6 <button class="menu-item" role="menuitem"> 7 Open 8 </button> 9 <button class="menu-item" role="menuitem"> 10 Rename 11 </button> 12 <button class="menu-item" role="menuitem"> 13 Duplicate 14 </button> 15 <div class="menu-divider" role="separator"> 16 </div> 17 <button class="menu-item danger" role="menuitem"> 18 <span class="menu-row"> 19 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"> 20 <polyline points="3 6 5 6 21 6"/> 21 <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/> 22 </svg> 23 Move to trash 24 </span> 25 </button> 26 </div> 27 </div>
Copy 1 .demo-area { 2 position:relative; 3 min-height:180px; 4 display:flex; 5 align-items:center; 6 justify-content:center; 7 background:#0A0A0A; 8 border:1px dashed #2A2A3E; 9 border-radius:8px; 10 font-family:system-ui, 11 sans-serif; 12 overflow:hidden 13 } 14 .trigger-hint { 15 color:#808080; 16 font-size:12px; 17 pointer-events:none 18 } 19 .context-menu { 20 position:fixed; 21 display:none; 22 min-width:180px; 23 background:#151515; 24 border:1px solid #2A2A3E; 25 border-radius:8px; 26 box-shadow:0 10px 30px rgba(0, 27 0, 28 0, 29 .5); 30 padding:6px 0; 31 z-index:50 32 } 33 .context-menu.open { 34 display:block 35 } 36 .menu-item { 37 width:100%; 38 padding:8px 14px; 39 border:none; 40 background:transparent; 41 color:#E0E0E0; 42 font-size:13px; 43 text-align:left; 44 cursor:pointer; 45 display:flex; 46 align-items:center; 47 justify-content:space-between; 48 gap:12px 49 } 50 .menu-item:hover:not(.danger) { 51 background:#1A1A2E; 52 color:#00FF41 53 } 54 .menu-item:focus { 55 outline:none; 56 background:#1A1A2E; 57 color:#00FF41 58 } 59 .menu-item.danger { 60 color:#EF4444 61 } 62 .menu-item.danger:hover { 63 background:rgba(239, 64 68, 65 68, 66 .1); 67 color:#F87171 68 } 69 .menu-item.danger:focus { 70 background:rgba(239, 71 68, 72 68, 73 .1); 74 color:#F87171 75 } 76 .menu-row { 77 display:flex; 78 align-items:center; 79 gap:8px 80 } 81 .menu-divider { 82 height:1px; 83 background:#2A2A3E; 84 margin:6px 8px 85 }
untitled.js wrap JavaScript
Copy 1 document.querySelectorAll('[data-contextmenu="danger"]').forEach(area=>{ 2 const menu=area.querySelector('.context-menu'); 3 const open=(x,y)=>{menu.style.left=x+'px';menu.style.top=y+'px';menu.classList.add('open');}; 4 area.addEventListener('contextmenu',e=>{e.preventDefault();open(e.clientX,e.clientY);}); 5 document.addEventListener('click',e=>{if(!menu.contains(e.target))menu.classList.remove('open');}); 6 document.addEventListener('keydown',e=>{if(e.key==='Escape')menu.classList.remove('open');}); 7 });
Copy 1 <div data-contextmenu="danger" class="relative min-h-[180px] flex items-center justify-center bg-[#0A0A0A] border border-dashed border-[#2A2A3E] rounded-lg overflow-hidden"> 2 <span class="text-xs text-[#808080] pointer-events-none"> 3 Right-click on this file 4 </span> 5 <div class="context-menu hidden fixed min-w-[180px] bg-[#151515] border border-[#2A2A3E] rounded-lg shadow-2xl py-1.5 z-50" role="menu" aria-label="File actions"> 6 <button class="w-full px-3.5 py-2 text-left text-[13px] text-[#E0E0E0] hover:bg-[#1A1A2E] hover:text-[#00FF41] focus:outline-none focus:bg-[#1A1A2E] focus:text-[#00FF41] flex items-center justify-between gap-3" role="menuitem"> 7 Open 8 </button> 9 <button class="w-full px-3.5 py-2 text-left text-[13px] text-[#E0E0E0] hover:bg-[#1A1A2E] hover:text-[#00FF41] focus:outline-none focus:bg-[#1A1A2E] focus:text-[#00FF41] flex items-center justify-between gap-3" role="menuitem"> 10 Rename 11 </button> 12 <button class="w-full px-3.5 py-2 text-left text-[13px] text-[#E0E0E0] hover:bg-[#1A1A2E] hover:text-[#00FF41] focus:outline-none focus:bg-[#1A1A2E] focus:text-[#00FF41] flex items-center justify-between gap-3" role="menuitem"> 13 Duplicate 14 </button> 15 <div class="h-px bg-[#2A2A3E] my-1.5 mx-2" role="separator"> 16 </div> 17 <button class="w-full px-3.5 py-2 text-left text-[13px] text-[#EF4444] hover:bg-[rgba(239,68,68,0.1)] hover:text-[#F87171] focus:outline-none focus:bg-[rgba(239,68,68,0.1)] focus:text-[#F87171] flex items-center justify-between gap-3" role="menuitem"> 18 <span class="inline-flex items-center gap-2"> 19 <svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 20 <polyline points="3 6 5 6 21 6"/> 21 <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/> 22 </svg> 23 Move to trash 24 </span> 25 </button> 26 </div> 27 </div>
Copy 1 <div data-contextmenu="danger" class="position-relative d-flex align-items-center justify-content-center border border-secondary border-dashed rounded overflow-hidden" style="min-height:180px;background:#0A0A0A"> 2 <span class="text-secondary small user-select-none"> 3 Right-click on this file 4 </span> 5 <div class="context-menu dropdown-menu dropdown-menu-dark p-1 shadow" role="menu" aria-label="File actions" style="--bs-dropdown-bg:#151515;--bs-dropdown-border-color:#2A2A3E;--bs-dropdown-link-color:#E0E0E0;--bs-dropdown-link-hover-bg:#1A1A2E;--bs-dropdown-link-hover-color:#00FF41;--bs-dropdown-link-active-bg:#00FF41;--bs-dropdown-link-active-color:#0A0A0A;display:none;position:fixed;min-width:180px;"> 6 <button class="dropdown-item" role="menuitem"> 7 Open 8 </button> 9 <button class="dropdown-item" role="menuitem"> 10 Rename 11 </button> 12 <button class="dropdown-item" role="menuitem"> 13 Duplicate 14 </button> 15 <div class="dropdown-divider" style="border-color:#2A2A3E"> 16 </div> 17 <button class="dropdown-item text-danger d-flex align-items-center gap-2" role="menuitem"> 18 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 19 <polyline points="3 6 5 6 21 6"/> 20 <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/> 21 </svg> 22 Move to trash 23 </button> 24 </div> 25 </div>
preview
React Component
A reusable React version that computes viewport-aware position, closes on outside click or Escape, and exposes items through props.
ContextMenu.tsx wrap TypeScript
Copy 1 "use client"; 2 import { useState, useRef, useEffect, useCallback } from "react"; 3 4 interface MenuItem { 5 label: string; 6 shortcut?: string; 7 danger?: boolean; 8 disabled?: boolean; 9 divider?: boolean; 10 action: () => void; 11 } 12 13 function ContextMenu({ items, children }: { items: MenuItem[]; children: React.ReactNode }) { 14 const [open, setOpen] = useState(false); 15 const [pos, setPos] = useState({ x: 0, y: 0 }); 16 const menuRef = useRef<HTMLDivElement>(null); 17 18 const clamp = useCallback((x: number, y: number) => { 19 const menuWidth = menuRef.current?.offsetWidth ?? 200; 20 const menuHeight = menuRef.current?.offsetHeight ?? items.length * 36; 21 const padding = 8; 22 return { 23 x: Math.max(padding, Math.min(x, window.innerWidth - menuWidth - padding)), 24 y: Math.max(padding, Math.min(y, window.innerHeight - menuHeight - padding)), 25 }; 26 }, [items.length]); 27 28 const handleContextMenu = useCallback((e: React.MouseEvent) => { 29 e.preventDefault(); 30 setPos(clamp(e.clientX, e.clientY)); 31 setOpen(true); 32 }, [clamp]); 33 34 useEffect(() => { 35 if (!open) return; 36 const close = () => setOpen(false); 37 const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") close(); }; 38 document.addEventListener("click", close); 39 document.addEventListener("keydown", onKey); 40 return () => { 41 document.removeEventListener("click", close); 42 document.removeEventListener("keydown", onKey); 43 }; 44 }, [open]); 45 46 return ( 47 <div onContextMenu={handleContextMenu} className="relative"> 48 {children} 49 {open && ( 50 <div 51 ref={menuRef} 52 role="menu" 53 className="fixed z-50 min-w-[180px] bg-[#151515] border border-[#2A2A3E] rounded-lg shadow-2xl py-1.5" 54 style={{ left: pos.x, top: pos.y }} 55 > 56 {items.map((item, i) => 57 item.divider ? ( 58 <div key={i} className="h-px bg-[#2A2A3E] my-1.5 mx-2" role="separator" /> 59 ) : ( 60 <button 61 key={i} 62 role="menuitem" 63 disabled={item.disabled} 64 onClick={() => { item.action(); setOpen(false); }} 65 className={`w-full px-4 py-2 text-left text-[13px] flex items-center justify-between disabled:opacity-40 ${ 66 item.danger 67 ? "text-[#EF4444] hover:bg-[rgba(239,68,68,0.1)] hover:text-[#F87171]" 68 : "text-[#E0E0E0] hover:bg-[#1A1A2E] hover:text-[#00FF41]" 69 }`} 70 > 71 <span>{item.label}</span> 72 {item.shortcut && <kbd className="font-mono text-[10px] text-[#525252] bg-[#0A0A0A] px-1.5 py-0.5 rounded border border-[#2A2A3E]">{item.shortcut}</kbd>} 73 </button> 74 ) 75 )} 76 </div> 77 )} 78 </div> 79 ); 80 }
Smart Positioning
A menu opened near the viewport edge must flip or clamp so it stays fully visible. Measure the rendered menu after opening, then adjust coordinates.
positioning.ts wrap TypeScript
Copy 1 function clampMenuPosition( 2 x: number, 3 y: number, 4 menuWidth: number, 5 menuHeight: number, 6 padding = 8, 7 ) { 8 return { 9 x: Math.max(padding, Math.min(x, window.innerWidth - menuWidth - padding)), 10 y: Math.max(padding, Math.min(y, window.innerHeight - menuHeight - padding)), 11 }; 12 } 13 14 // Keyboard trigger: Shift+F10 or the Menu key. 15 function handleKeyboardTrigger(target: HTMLElement, openAt: (x: number, y: number) => void) { 16 const rect = target.getBoundingClientRect(); 17 openAt(rect.left + rect.width / 2, rect.top + rect.height / 2); 18 } 19 20 // After the menu renders, re-clamp if the first guess overflowed. 21 useEffect(() => { 22 if (!open || !menuRef.current) return; 23 const rect = menuRef.current.getBoundingClientRect(); 24 setPos(p => clampMenuPosition(p.x, p.y, rect.width, rect.height)); 25 }, [open]);
Accessibility
Context menus are high-risk for keyboard and screen-reader users because they are not part of the normal tab order. Follow these practical checks to keep them usable.
Use role="menu" on the container and role="menuitem" on each action. Give the menu an accessible name with aria-label or aria-labelledby . Trap focus inside the open menu; loop with ArrowUp / ArrowDown . Support the native keyboard trigger: Shift+F10 or the Menu key. Close on Escape , outside click, and after any item is activated. Mark disabled items with disabled or aria-disabled="true" and skip them during arrow navigation. Place a visible focus ring on the active item (color change alone is not enough). For submenus, toggle aria-expanded and move focus into the submenu on open. $ Blueprint — Engineering Documentation · Section ID: UI-CM-01 · Revision: 1.1