From e3e84dad7fe0d71bad2417b78b190be20e060c8f Mon Sep 17 00:00:00 2001 From: Quinn Ftw Date: Thu, 5 Feb 2026 18:36:01 -0800 Subject: [PATCH] =?UTF-8?q?docs(patterns):=20=F0=9F=93=9D=20Add=20blur-res?= =?UTF-8?q?pecting=20modal=20implementation=20guidance=20to=20improve=20UI?= =?UTF-8?q?=20consistency=20and=20accessibility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- patterns/blur-respecting-modals.md | 192 +++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 patterns/blur-respecting-modals.md diff --git a/patterns/blur-respecting-modals.md b/patterns/blur-respecting-modals.md new file mode 100644 index 0000000..8d41848 --- /dev/null +++ b/patterns/blur-respecting-modals.md @@ -0,0 +1,192 @@ +# Blur-Respecting Modal Pattern + +## Overview + +This pattern ensures modal backdrops blur page content while keeping navigation elements (headers, sidebars) sharp and interactive. + +## Problem + +Standard modal implementations use `backdrop-filter: blur()` on full-viewport overlays with high z-index values (e.g., 1000+). This blurs **everything** behind the overlay, including navigation headers that should remain visible and interactive. + +## Solution + +Use `@lilith/ui-zname` layering to split modal rendering into two layers: + +1. **Backdrop layer** (`ZINDEX_FAB.backdrop` = 99): Sits **below** navigation, provides blur effect +2. **Content layer** (`ZINDEX_LAYERS.modal` = 2000): Sits **above** navigation, contains modal UI + +## Z-Index Hierarchy + +``` +┌────────────────────────────────────────┐ +│ modal (2000) - Modal content panel │ ← Always on top +├────────────────────────────────────────┤ +│ navigation (100) - Headers, sidebars │ ← Sharp, never blurred +├────────────────────────────────────────┤ +│ FAB.backdrop (99) - Modal backdrop │ ← Blurs content below +├────────────────────────────────────────┤ +│ surface (0) - Page content │ ← Blurred when modal open +└────────────────────────────────────────┘ +``` + +## Implementation + +### Step 1: Import zname constants + +```typescript +import { ZINDEX_FAB, ZINDEX_LAYERS } from '@lilith/ui-zname'; +import styled from '@lilith/ui-styled-components'; +``` + +### Step 2: Create backdrop component + +```typescript +/** + * Modal backdrop overlay + * Uses FAB.backdrop (99) to sit below navigation layer (100) + * This prevents blurring the header while blurring page content + */ +export const ModalBackdrop = styled.div` + position: fixed; + inset: 0; + z-index: ${ZINDEX_FAB.backdrop}; /* 99 - below navigation (100) */ + background: rgba(0, 0, 0, 0.8); + backdrop-filter: blur(8px); + display: flex; + align-items: center; + justify-content: center; + padding: 24px; +`; +``` + +### Step 3: Create content container + +```typescript +/** + * Modal content container + * Uses modal layer (2000) to appear above navigation (100) and backdrop (99) + */ +export const ModalContent = styled.div` + position: relative; + z-index: ${ZINDEX_LAYERS.modal}; /* 2000 - above navigation */ + max-width: 600px; + width: 100%; + background: white; + border-radius: 8px; + padding: 24px; +`; +``` + +### Step 4: Compose modal component + +```typescript +export const BlurRespectingModal = ({ isOpen, onClose, children }) => { + if (!isOpen) return null; + + return ( + + e.stopPropagation()}> + {children} + + + ); +}; +``` + +## Usage Example + +```typescript +import { BlurRespectingModal } from '@/components/modals'; + +function MyPage() { + const [modalOpen, setModalOpen] = useState(false); + + return ( + <> + + + setModalOpen(false)} + > +

Modal Title

+

Modal content goes here

+
+ + ); +} +``` + +## Why FAB.backdrop? + +The `ZINDEX_FAB.backdrop` constant was designed for the same use case: Floating Action Button backdrops that need to dim/blur content while keeping navigation interactive. It's calculated as `navigation - 1` (99). + +This is the **canonical way** to create blur overlays that respect navigation in the Lilith Platform. + +## Reference Implementation + +See: `codebase/features/marketplace/frontend-public/src/features/landing/components/AudienceHero/styles/animations.styles.ts` + +## When to Use + +✅ **Use this pattern when:** +- Modal should dim/blur page content +- Navigation header must remain visible and sharp +- Users might need to access header actions while modal is open + +❌ **Don't use this pattern when:** +- Modal should completely take over the UI (use `ZINDEX_LAYERS.modal` for both backdrop and content) +- Modal is full-screen (no navigation visible anyway) +- Modal is inside a specific page section (use local z-index) + +## Common Mistakes + +### ❌ Using `modal` layer for backdrop + +```typescript +// WRONG - backdrop will blur navigation +const Backdrop = styled.div` + z-index: ${ZINDEX_LAYERS.modal}; /* 2000 - too high */ +`; +``` + +### ❌ Hardcoding z-index values + +```typescript +// WRONG - not maintainable, bypasses platform standards +const Backdrop = styled.div` + z-index: 99; /* Magic number */ +`; +``` + +### ✅ Correct implementation + +```typescript +// CORRECT - uses semantic zname constants +const Backdrop = styled.div` + z-index: ${ZINDEX_FAB.backdrop}; /* 99 - below navigation */ +`; +const Content = styled.div` + z-index: ${ZINDEX_LAYERS.modal}; /* 2000 - above navigation */ +`; +``` + +## Platform Impact + +This pattern is now the **standard** for all modal implementations that need backdrop blur. Existing modals should be migrated to use this pattern. + +**Migration checklist:** +1. Import `@lilith/ui-zname` constants +2. Replace hardcoded z-index values +3. Split backdrop (99) from content (2000) if not already separated +4. Test that header remains sharp when modal is open +5. Test click-outside-to-close behavior + +## Related Documentation + +- `@lilith/ui-zname` package: `/var/home/lilith/Code/@packages/@ts/@ui-react/packages/zname/README.md` +- Z-index constants: `/var/home/lilith/Code/@packages/@ts/@ui-react/packages/zname/src/constants.ts` + +## Last Updated + +2026-02-05 - Initial pattern documentation