$npx -y skills add fayazara/macos-app-skills --skill notch-uiAdd a Dynamic Island-style notch UI to a macOS app. Use this skill whenever the user wants to create a notch overlay, notch extender, Dynamic Island for Mac, notch indicator, or any UI that extends from the MacBook notch area. Also trigger when the user mentions "notch shape", "n
| 1 | # macOS Notch UI (Dynamic Island Style) |
| 2 | |
| 3 | This skill creates a Dynamic Island-style overlay that extends from the MacBook's hardware notch. The overlay is a transparent floating panel positioned flush against the top of the screen, using a custom shape with concave "ear" curves that blend seamlessly with the physical notch cutout. |
| 4 | |
| 5 | ## Architecture |
| 6 | |
| 7 | The implementation has 3 parts: |
| 8 | |
| 9 | 1. **`NotchWindow` (NSPanel subclass)** -- a borderless, transparent, click-through panel at `CGShieldingWindowLevel` that sits above everything, including the menu bar |
| 10 | 2. **`NotchShape` (SwiftUI Shape)** -- draws the Dynamic Island silhouette with concave quadratic Bezier curves at the top corners and convex rounded corners at the bottom |
| 11 | 3. **Your content view** -- whatever you want to show inside the notch (status indicators, waveforms, text, icons) |
| 12 | |
| 13 | ## How It Works |
| 14 | |
| 15 | The MacBook notch is a black rectangle at the top-center of the screen. By placing a black-filled `NotchShape` at that exact position at the highest window level, it visually extends the notch area. Content inside the shape appears to "emerge" from the hardware notch. |
| 16 | |
| 17 | The key positioning math: |
| 18 | |
| 19 | ```swift |
| 20 | // Use full screen frame (not visibleFrame) to include the menu bar / notch area |
| 21 | let screen = NSScreen.main! |
| 22 | let x = screen.frame.origin.x + (screen.frame.width - totalWidth) / 2 |
| 23 | let y = screen.frame.origin.y + screen.frame.height - totalHeight |
| 24 | ``` |
| 25 | |
| 26 | Using `screen.frame` (not `screen.visibleFrame`) is critical -- `visibleFrame` excludes the menu bar area where the notch lives. |
| 27 | |
| 28 | ## Reference Files |
| 29 | |
| 30 | - `references/NotchWindow.swift` -- Drop-in NSPanel subclass with show/hide and positioning |
| 31 | - `references/NotchShape.swift` -- The Dynamic Island shape with animatable corner radii |
| 32 | |
| 33 | ## Step-by-Step Integration |
| 34 | |
| 35 | ### 1. Add the NotchShape |
| 36 | |
| 37 | Copy `references/NotchShape.swift`. This is a SwiftUI `Shape` with two configurable corner radii: |
| 38 | |
| 39 | - `topCornerRadius` (default 10) -- the concave "ear" curves at the top that mimic the hardware notch's inverse corners |
| 40 | - `bottomCornerRadius` (default 16) -- the standard convex rounded corners at the bottom |
| 41 | |
| 42 | Both are animatable via `animatableData`, so SwiftUI can smoothly interpolate shape changes. |
| 43 | |
| 44 | ### 2. Create Your Content View |
| 45 | |
| 46 | Build whatever you want to show inside the notch. The content should be clipped to `NotchShape` and filled with black background: |
| 47 | |
| 48 | ```swift |
| 49 | struct NotchContentView: View { |
| 50 | var isVisible: Bool |
| 51 | |
| 52 | var body: some View { |
| 53 | HStack { |
| 54 | Image(systemName: "mic.fill") |
| 55 | .foregroundStyle(.red) |
| 56 | Text("Recording") |
| 57 | .font(.system(size: 13, weight: .medium)) |
| 58 | .foregroundStyle(.white) |
| 59 | } |
| 60 | .frame(width: isVisible ? 200 : 0) |
| 61 | .frame(height: 32) |
| 62 | .background(NotchShape().fill(.black)) |
| 63 | .clipShape(NotchShape()) |
| 64 | .animation(.spring(response: 0.35, dampingFraction: 0.75), value: isVisible) |
| 65 | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) |
| 66 | } |
| 67 | } |
| 68 | ``` |
| 69 | |
| 70 | ### 3. Add the NotchWindow |
| 71 | |
| 72 | Copy `references/NotchWindow.swift`. This is a generic NSPanel that: |
| 73 | |
| 74 | - Creates a borderless, transparent, non-activating panel |
| 75 | - Positions at `CGShieldingWindowLevel` (above everything) |
| 76 | - Centers at the top of the screen, flush with the top edge |
| 77 | - Ignores mouse events (fully click-through) |
| 78 | - Joins all spaces and survives fullscreen |
| 79 | |
| 80 | ### 4. Show and Hide |
| 81 | |
| 82 | ```swift |
| 83 | // Create once and reuse |
| 84 | let notchWindow = NotchWindow() |
| 85 | |
| 86 | // Show with your content |
| 87 | let content = NotchContentView(isVisible: true) |
| 88 | notchWindow.showNotch(content: content) |
| 89 | |
| 90 | // Hide with spring collapse animation |
| 91 | notchWindow.hideNotch() |
| 92 | ``` |
| 93 | |
| 94 | ## Spring Animation Choreography |
| 95 | |
| 96 | The Dynamic Island effect comes from a specific animation sequence: |
| 97 | |
| 98 | **Show:** |
| 99 | 1. Window appears instantly (`orderFront`) |
| 100 | 2. On the next runloop tick, `isVisible` toggles to `true` |
| 101 | 3. The width animates from 0 to the target width with `.spring(response: 0.35, dampingFraction: 0.75)` |
| 102 | |
| 103 | This two-step approach (instant window, then animated content) is necessary because SwiftUI needs the view to be in the hierarchy before it can animate. |
| 104 | |
| 105 | **Hide (3-step choreography):** |
| 106 | 1. Clear any expanded content (text, details) -- collapses to compact shape |
| 107 | 2. After 0.25s, set `isVisible = false` -- triggers the spring width collapse to 0 |
| 108 | 3. A |