|$ curl https://forge-ai.dev/api/markdown?path=docs/components/modals
$cat docs/modals.md
updated Recently·14 min read·published
Modals
◆CSS◆HTML◆UI◆Advanced
Modal Components
Modal dialogs focus user attention on a specific task or message. These examples use clean CSS overlays with backdrop blur, smooth entrance animations, and accessible markup — all without any JavaScript framework.
Dialog Modal
A confirmation dialog with backdrop overlay, centered layout, and action buttons. The overlay prevents interaction with the page behind it.
confirmation-dialog
Live
untitled.html
HTML
| 1 | <div class="modal-demo"> |
| 2 | <button class="open-btn"> |
| 3 | Open Modal |
| 4 | </button> |
| 5 | <div class="modal-wrapper" id="demo-modal"> |
| 6 | <div class="modal-backdrop"> |
| 7 | </div> |
| 8 | <div class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-title"> |
| 9 | <div class="modal-header"> |
| 10 | <h3 id="modal-title" class="modal-title"> |
| 11 | Confirm Action |
| 12 | </h3> |
| 13 | <button class="modal-close" aria-label="Close"> |
| 14 | ✕ |
| 15 | </button> |
| 16 | </div> |
| 17 | <p class="modal-desc"> |
| 18 | Are you sure you want to delete this item? This action cannot be undone. All associated data will be permanently removed. |
| 19 | </p> |
| 20 | <div class="modal-footer"> |
| 21 | <button class="btn btn-secondary"> |
| 22 | Cancel |
| 23 | </button> |
| 24 | <button class="btn btn-danger"> |
| 25 | Delete |
| 26 | </button> |
| 27 | </div> |
| 28 | </div> |
| 29 | </div> |
| 30 | </div> |
untitled.css
CSS
| 1 | .modal-demo { |
| 2 | position:relative; |
| 3 | min-height:280px; |
| 4 | display:flex; |
| 5 | align-items:center; |
| 6 | justify-content:center; |
| 7 | font-family:system-ui, |
| 8 | sans-serif |
| 9 | } |
| 10 | .open-btn { |
| 11 | padding:12px 28px; |
| 12 | border-radius:8px; |
| 13 | font-size:14px; |
| 14 | font-weight:600; |
| 15 | background:#00FF41; |
| 16 | color:#0A0A0A; |
| 17 | border:none; |
| 18 | cursor:pointer; |
| 19 | transition:all .2s |
| 20 | } |
| 21 | .open-btn:hover { |
| 22 | transform:translateY(-2px); |
| 23 | box-shadow:0 8px 24px rgba(0, |
| 24 | 0, |
| 25 | 0, |
| 26 | .3) |
| 27 | } |
| 28 | .modal-wrapper { |
| 29 | position:absolute; |
| 30 | inset:0; |
| 31 | display:none; |
| 32 | align-items:center; |
| 33 | justify-content:center; |
| 34 | padding:24px |
| 35 | } |
| 36 | .modal-backdrop { |
| 37 | position:absolute; |
| 38 | inset:0; |
| 39 | background:rgba(0, |
| 40 | 0, |
| 41 | 0, |
| 42 | .6); |
| 43 | backdrop-filter:blur(4px) |
| 44 | } |
| 45 | .modal { |
| 46 | position:relative; |
| 47 | background:#111; |
| 48 | border:1px solid #2A2A3E; |
| 49 | border-radius:12px; |
| 50 | padding:0; |
| 51 | max-width:400px; |
| 52 | width:100%; |
| 53 | box-shadow:0 24px 64px rgba(0, |
| 54 | 0, |
| 55 | 0, |
| 56 | .5); |
| 57 | animation:modalIn .25s ease |
| 58 | } |
| 59 | @keyframes modalIn { |
| 60 | from { |
| 61 | opacity:0; |
| 62 | transform:scale(.95) translateY(10px) |
| 63 | } |
| 64 | to { |
| 65 | opacity:1; |
| 66 | transform:scale(1) translateY(0) |
| 67 | } |
| 68 | } |
| 69 | .modal-header { |
| 70 | display:flex; |
| 71 | align-items:center; |
| 72 | justify-content:space-between; |
| 73 | padding:20px 24px 0 |
| 74 | } |
| 75 | .modal-title { |
| 76 | font-size:16px; |
| 77 | font-weight:700; |
| 78 | color:#E0E0E0; |
| 79 | margin:0 |
| 80 | } |
| 81 | .modal-close { |
| 82 | width:28px; |
| 83 | height:28px; |
| 84 | border-radius:6px; |
| 85 | border:none; |
| 86 | background:transparent; |
| 87 | color:#808080; |
| 88 | cursor:pointer; |
| 89 | display:flex; |
| 90 | align-items:center; |
| 91 | justify-content:center; |
| 92 | font-size:14px; |
| 93 | transition:all .2s |
| 94 | } |
| 95 | .modal-close:hover { |
| 96 | background:rgba(255, |
| 97 | 255, |
| 98 | 255, |
| 99 | .05); |
| 100 | color:#E0E0E0 |
| 101 | } |
| 102 | .modal-desc { |
| 103 | font-size:13px; |
| 104 | color:#808080; |
| 105 | margin:12px 24px 20px; |
| 106 | line-height:1.5 |
| 107 | } |
| 108 | .modal-footer { |
| 109 | display:flex; |
| 110 | gap:8px; |
| 111 | justify-content:flex-end; |
| 112 | padding:16px 24px; |
| 113 | border-top:1px solid #222 |
| 114 | } |
| 115 | .btn { |
| 116 | padding:8px 16px; |
| 117 | border-radius:6px; |
| 118 | font-size:12px; |
| 119 | font-weight:600; |
| 120 | font-family:inherit; |
| 121 | cursor:pointer; |
| 122 | border:none; |
| 123 | transition:all .2s |
| 124 | } |
| 125 | .btn-secondary { |
| 126 | background:transparent; |
| 127 | color:#A0A0A0; |
| 128 | border:1px solid #2A2A3E |
| 129 | } |
| 130 | .btn-secondary:hover { |
| 131 | background:rgba(255, |
| 132 | 255, |
| 133 | 255, |
| 134 | .05); |
| 135 | color:#E0E0E0; |
| 136 | border-color:#3A3A4E |
| 137 | } |
| 138 | .btn-danger { |
| 139 | background:#EF4444; |
| 140 | color:#fff |
| 141 | } |
| 142 | .btn-danger:hover { |
| 143 | background:#DC2626 |
| 144 | } |
untitled.js
JavaScript
| 1 | const w=document.getElementById('demo-modal');const ob=document.querySelector('.open-btn');ob.addEventListener('click',()=>w.style.display='flex');w.querySelectorAll('.modal-close,.btn-secondary').forEach(el=>el.addEventListener('click',()=>w.style.display='none'));w.querySelector('.modal-backdrop').addEventListener('click',()=>w.style.display='none');document.addEventListener('keydown',e=>{if(e.key==='Escape'&&w.style.display==='flex')w.style.display='none'}) |
Slide-Out Panel
A slide-out panel from the right side — useful for detail views, settings, and navigation drawers. Uses CSS transform for smooth animation.
slide-panel
Live
untitled.html
HTML
| 1 | <div class="panel-demo"> |
| 2 | <div class="panel-backdrop"> |
| 3 | </div> |
| 4 | <div class="panel"> |
| 5 | <div class="panel-header"> |
| 6 | <h3 class="panel-title"> |
| 7 | Settings |
| 8 | </h3> |
| 9 | <button class="panel-close" aria-label="Close panel"> |
| 10 | ✕ |
| 11 | </button> |
| 12 | </div> |
| 13 | <div class="panel-body"> |
| 14 | <p> |
| 15 | Notification preferences, account settings, and application configuration options are displayed in this slide-out panel. |
| 16 | </p> |
| 17 | <p> |
| 18 | Slide-out panels are ideal for secondary content that doesn't need a full page navigation — keeping the user's context intact. |
| 19 | </p> |
| 20 | </div> |
| 21 | </div> |
| 22 | </div> |
untitled.css
CSS
| 1 | .panel-demo { |
| 2 | |
| 3 | position: relative; |
| 4 | |
| 5 | min-height: 300px; |
| 6 | |
| 7 | overflow: hidden; |
| 8 | |
| 9 | font-family: system-ui, sans-serif; |
| 10 | |
| 11 | } |
| 12 | .panel-backdrop { |
| 13 | |
| 14 | position: absolute; |
| 15 | |
| 16 | inset: 0; |
| 17 | |
| 18 | background: rgba(0, |
| 19 | 0, |
| 20 | 0, |
| 21 | 0.5); |
| 22 | |
| 23 | } |
| 24 | .panel { |
| 25 | |
| 26 | position: absolute; |
| 27 | |
| 28 | top: 0; |
| 29 | |
| 30 | right: 0; |
| 31 | |
| 32 | bottom: 0; |
| 33 | |
| 34 | width: 280px; |
| 35 | |
| 36 | background: #111; |
| 37 | |
| 38 | border-left: 1px solid #2A2A3E; |
| 39 | |
| 40 | display: flex; |
| 41 | |
| 42 | flex-direction: column; |
| 43 | |
| 44 | box-shadow: -8px 0 32px rgba(0, |
| 45 | 0, |
| 46 | 0, |
| 47 | 0.3); |
| 48 | |
| 49 | animation: slideIn 0.3s ease; |
| 50 | |
| 51 | } |
| 52 | @keyframes slideIn { |
| 53 | |
| 54 | from { |
| 55 | transform: translateX(100%); |
| 56 | } |
| 57 | |
| 58 | to { |
| 59 | transform: translateX(0); |
| 60 | } |
| 61 | |
| 62 | } |
| 63 | .panel-header { |
| 64 | |
| 65 | padding: 20px; |
| 66 | |
| 67 | border-bottom: 1px solid #222; |
| 68 | |
| 69 | display: flex; |
| 70 | |
| 71 | align-items: center; |
| 72 | |
| 73 | justify-content: space-between; |
| 74 | |
| 75 | } |
| 76 | .panel-title { |
| 77 | |
| 78 | font-size: 15px; |
| 79 | |
| 80 | font-weight: 600; |
| 81 | |
| 82 | color: #E0E0E0; |
| 83 | |
| 84 | margin: 0; |
| 85 | |
| 86 | } |
| 87 | .panel-close { |
| 88 | |
| 89 | width: 28px; |
| 90 | |
| 91 | height: 28px; |
| 92 | |
| 93 | border-radius: 6px; |
| 94 | |
| 95 | border: none; |
| 96 | |
| 97 | background: transparent; |
| 98 | |
| 99 | color: #808080; |
| 100 | |
| 101 | cursor: pointer; |
| 102 | |
| 103 | display: flex; |
| 104 | |
| 105 | align-items: center; |
| 106 | |
| 107 | justify-content: center; |
| 108 | |
| 109 | font-size: 14px; |
| 110 | |
| 111 | transition: all 0.2s; |
| 112 | |
| 113 | } |
| 114 | .panel-close:hover { |
| 115 | |
| 116 | background: rgba(255, |
| 117 | 255, |
| 118 | 255, |
| 119 | 0.05); |
| 120 | |
| 121 | color: #E0E0E0; |
| 122 | |
| 123 | } |
| 124 | .panel-body { |
| 125 | |
| 126 | padding: 20px; |
| 127 | |
| 128 | flex: 1; |
| 129 | |
| 130 | overflow-y: auto; |
| 131 | |
| 132 | } |
| 133 | .panel-body p { |
| 134 | |
| 135 | font-size: 13px; |
| 136 | |
| 137 | color: #808080; |
| 138 | |
| 139 | margin: 0 0 12px 0; |
| 140 | |
| 141 | line-height: 1.6; |
| 142 | |
| 143 | } |
HTML Structure
The modal follows a semantic structure: backdrop overlay, dialog container with role="dialog", header with title and close button, content area, and footer with actions. Always use aria-modal="true" and aria-labelledby to connect the title.
modal-structure.html
HTML
| 1 | <div class="modal-backdrop"><!-- background overlay --></div> |
| 2 | <div class="modal" role="dialog" aria-modal="true" |
| 3 | aria-labelledby="dialog-title"> |
| 4 | <div class="modal-header"> |
| 5 | <h3 id="dialog-title" class="modal-title"> |
| 6 | Confirm Action |
| 7 | </h3> |
| 8 | <button class="modal-close" aria-label="Close">✕</button> |
| 9 | </div> |
| 10 | <p class="modal-desc"> |
| 11 | Are you sure you want to proceed? |
| 12 | </p> |
| 13 | <div class="modal-footer"> |
| 14 | <button class="btn">Cancel</button> |
| 15 | <button class="btn btn-primary">Confirm</button> |
| 16 | </div> |
| 17 | </div> |
Best Practices
- Use role="dialog" and aria-modal="true" for screen reader identification.
- Traps focus inside the modal when open — Tab should cycle through modal elements only.
- Close on Escape key and backdrop click (light-dismiss) for user convenience.
- Avoid stacking multiple modals — use a queue or replace the current one.
- Animate entrance with opacity and transform for smooth UX, but keep it brief (200-300ms).