electron-dynamic-island
v1.1.0
Published
A robust, drop-in Dynamic Island notification system for macOS Electron apps.
Maintainers
Readme
Table of Contents
- Features
- Installation
- Usage
- Animations
- Customization
- Built-in Icons
- Notification Types
- API Reference
- How It Works
- Technical Note
- Origin
- Running Examples
- License
Features
- Hardware Notch Integration: Automatically detects and aligns with the MacBook notch.
- Robust Sizing:
- Dynamic Mode: Expands fluidly to fit content (icon + text) while maintaining perfect optical alignment.
- Fixed Mode: Standard notification size for consistent UI.
- Native Animations: Smooth, spring-based animations for entry, exit, and state changes.
- Sound System: Integrated sound effects for success, error, and connection states.
- TypeScript Ready: Includes built-in type definitions (
index.d.ts). This provides full IntelliSense and autocomplete support in VS Code, even if you're writing plain JavaScript.
Installation
npm install electron-dynamic-islandUsage
1. Initialize in Main Process
Initialize the island in your main.js or index.js after the app is ready.
const { app } = require('electron');
const { DynamicIsland } = require('electron-dynamic-island');
let island;
app.whenReady().then(() => {
island = new DynamicIsland({
debug: false, // Enable for logging
enableSounds: true,
sizing: { type: 'fixed' } // Default mode
});
island.init();
});2. Trigger Notifications
You can trigger notifications from anywhere in your main process.
Basic Success Notification
island.show({
type: 'success',
message: 'AirPods Connected',
icon: 'bluetooth' // Built-in icon
});Dynamic Mode (Auto-expanding)
Use dynamic mode for content that varies in length or icon size. The window will automatically resize and center itself.
island.show({
type: 'info',
message: 'Downloading Update...',
iconSize: 40, // Larger icon
sizing: {
type: 'dynamic',
height: true, // Allow height expansion
width: true // Allow width expansion
}
});Custom Icon & Sound
island.show({
type: 'success',
message: 'Device Paired',
icon: '/path/to/icon.png', // Local file path
soundFile: '/path/to/sound.mp3',
duration: 3000
});3. Best Practice: Check Support
Since the Dynamic Island only works on MacBooks with a notch and on the internal display, you should check for support before triggering it. If not supported, fallback to a standard Electron notification.
if (island.isSupported()) {
island.show({
type: 'success',
message: 'Connected'
});
} else {
// Fallback for older Macs, external displays, or Windows/Linux
new Notification({ title: 'Connected', body: 'Device connected successfully' }).show();
}4. Advanced Usage
Triggering on Events
You can hook the island into any Electron event or your own application logic.
// Example: Show notification when a file download completes
ipcMain.on('download-complete', (event, fileName) => {
island.show({
type: 'success',
message: `${fileName} Downloaded`,
icon: 'usb-c'
});
});
// Example: Show warning on battery low
const { powerMonitor } = require('electron');
powerMonitor.on('on-battery', () => {
island.show({
type: 'warning',
message: 'On Battery Power',
icon: 'warning'
});
});Handling Multiple Notifications
If you call island.show() while a notification is already visible, the new notification will instantly replace the current one with a smooth transition. This is useful for updating status (e.g., "Connecting..." -> "Connected").
// 1. Show initial state
island.show({
type: 'info',
message: 'Connecting...',
icon: 'spin', // Built-in spinner animation
animation: 'spin'
});
// 2. Update state after operation finishes
setTimeout(() => {
island.show({
type: 'success',
message: 'Connected'
});
}, 2000);
5. Animations
The library includes several native-feeling animations for the icon. Pass the name to the animation option in show().
| Animation | Description |
| :--- | :--- |
| pulse | A soft, breathing pulse effect. |
| bounce | A playful vertical bounce. |
| spin | A smooth 360-degree rotation. |
| wobble | A quirky back-and-forth shake. |
| fade | A simple, elegant fade-in. |
| slide | Slides down from the top. |
| none | Disables all icon animations. |
6. Customization
You can fine-tune the look to match your app's design. If you don't provide these values, the library uses native macOS defaults.
| Option | Default Value | Description |
| :--- | :--- | :--- |
| fontFamily | -apple-system (SF Pro) | Matches the system UI font. |
| fontSize | 14px | Standard macOS notification text size. |
| color | #FFFFFF (White) | High-contrast white text. |
| iconGap | 18px | Standard spacing between icon and text. |
island.show({
message: 'System Alert',
type: 'error',
// Overrides:
fontFamily: 'Helvetica Neue, sans-serif',
fontSize: '12px',
color: '#FF0000',
iconGap: 24
});7. Built-in Icons
The library comes with a set of polished, native-style SVG icons. You can use them by passing their name to the icon property.
| Icon Name | Description |
| :--- | :--- |
| check | A bold checkmark (default for success). |
| usb-c | A minimalist USB-C port symbol. |
| x | A crisp X symbol (default for error). |
| warning | A standard warning triangle. |
| info | A circle with an 'i'. |
| bluetooth | A minimalist Bluetooth symbol. |
// Use a built-in icon explicitly
island.show({
message: 'Bluetooth On',
icon: 'bluetooth'
});8. Notification Types
The type option determines the default color, icon, and sound for the notification.
| Type | Color | Default Icon | Default Sound | Glow Effect | Default Animation |
| :--- | :--- | :--- | :--- | :--- | :--- |
| success | Green (#4CD964) | Checkmark | Success Chime | Yes (Green) | pop-in |
| error | Red (#FF3B30) | X Symbol | Error Sound | Yes (Red) | shake |
| warning | Yellow (#FFD60A) | Warning Triangle | None | Yes (Yellow) | pop-in |
| info | Blue (#0A84FF) | Info Circle | None | Yes (Blue) | pop-in |
Note: You can override any of these defaults by passing specific color, icon, or soundFile options.
Tip: You can disable the glow effect by setting glow: false. You can also override the animation by passing animation: 'none' or another type.
API Reference
new DynamicIsland(options)
| Option | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| enableSounds | boolean | true | Play built-in sounds. |
| sizing | object | { type: 'fixed' } | Default sizing configuration. |
| debug | boolean | false | Log internal debug messages to the console. |
| devMode | boolean | false | Show a red dashed boundary around the window for visual debugging. |
island.isSupported()
Returns true if the app is running on a Mac with a notch and the internal display is active. Use this to determine whether to show the Dynamic Island or a fallback notification.
island.show(options)
| Option | Type | Description |
| :--- | :--- | :--- |
| type | 'success' \| 'error' \| 'info' \| 'warning' | Determines color and default icon. |
| message | string | The text to display. |
| icon | string | Path to image, SVG string, or preset ('bluetooth'). |
| iconSize | number | Size of the icon (width & height) in pixels. |
| iconWidth | number | Width of the icon in pixels (overrides iconSize). |
| iconHeight | number | Height of the icon in pixels (overrides iconSize). |
| sizing | object | Override sizing configuration (see below). |
| duration | number | Time in ms before auto-hiding (default 4500). |
| animation | string | Animation type (see Animations section). |
| fontFamily | string | Custom font family for the message. |
| fontSize | string | Custom font size (e.g., '14px'). |
| color | string | Custom text color (hex, rgb, or name). |
| glow | boolean | Enable/disable the icon glow effect (default true). |
| iconGap | number | Space between icon and text in pixels. |
| soundFile | string | Path to custom sound file. |
| devMode | boolean | Override global devMode setting. |
Icon Sizing
- Square Icons: Use
iconSize(e.g.,40) to set both width and height. - Non-Square Icons: Use
iconWidthandiconHeightfor custom dimensions (e.g., landscape images).
Example: Landscape Icon (AirPods)
island.show({
message: 'AirPods Pro',
icon: '/path/to/airpods.png',
iconWidth: 100,
iconHeight: 50, // Prevents extra vertical space
sizing: { type: 'dynamic', height: true, width: true }
});Sizing Modes
1. Fixed Mode (Default)
The notification stays at a consistent, standard size (height: 100px).
- Best for: Simple alerts, consistent UI feel.
- Behavior:
- Text: Long text is truncated with an ellipsis (...).
- Icons: Constrained to the container. Large icons will be clipped if they exceed the fixed height.
2. Dynamic Mode
The notification fluidly expands to fit your content.
- Best for: Variable content, large images, multi-line text.
- Behavior:
- Height: Calculates exact height needed based on
iconHeight+ padding + notch clearance. - Width: Expands horizontally if
sizing.width: true. - Alignment: Optically centers content below the hardware notch using asymmetric padding.
- Height: Calculates exact height needed based on
Sizing Configuration
The sizing object controls how the notification behaves.
{
type: 'dynamic', // 'fixed' or 'dynamic'
height: true, // Allow height to expand (Dynamic Mode only)
width: true, // Allow width to expand (Dynamic Mode only)
notchHeight: 40, // Override detected notch height (optional)
verticalOffset: 0 // Fine-tune vertical position (pixels)
}How It Works
- Detection: The library checks if the app is running on a compatible MacBook (M1 Pro/Max, M2/M3 Air/Pro) using system model identifiers.
- Positioning: It calculates the screen center and aligns the notification window directly under the hardware notch.
- Optical Alignment: In Dynamic Mode, the library uses asymmetric padding (more top padding) to optically center the content below the physical notch, ensuring it looks balanced to the human eye.
- Window Management: The Electron window is transparent and click-through, resizing dynamically based on the content to prevent clipping while maintaining performance.
Technical Note
It is important to note that macOS does not have a public API for "Dynamic Island" interactions. Native apps like Alcove and libraries like DynamicNotchKit leverage private system APIs or low level Swift windowing controls to achieve this effect, which are not directly accessible to Electron.
This library is "smart" because it reverse engineers that behavior for the web stack. It calculates the precise screen coordinates of the notch (which Electron does not natively detect) and manages a transparent, click through window that sits on top of everything.
Visually, it creates the illusion of the notch "expanding" by drawing a seamless black overlay that extends downwards and outwards. It uses hardware accelerated CSS transitions to mimic the exact spring physics of macOS, allowing an Electron app to feel just as native as a Swift application without the need for native code.
Origin
The core logic for this library was originally developed for Serial Bridge to handle device connection and disconnection status updates. When I couldn't find any existing solution to achieve this effect in Electron, I built it from scratch. After extensive testing and refinement within that application, I decided to extract, polish, and expand the functionality into this standalone library to help other Electron developers build buttery smooth, native experiences.
Running Examples
The repository includes example scripts to demonstrate the library's capabilities.
Basic Example: Shows a custom icon (AirPods) with dynamic sizing.
npm startAdvanced Example: Demonstrates sequential updates (Loading -> Success) and event triggering.
npm run example:advanced
License
MIT
