Skip to content

A highly customizable 'Dynamic Island' style notification system for Electron apps on macOS.

License

Notifications You must be signed in to change notification settings

IrtizaNasar/electron-dynamic-island

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Electron Dynamic Island Logo

Electron Dynamic Island

npm version

A robust, and highly customizable "Dynamic Island" style notification system for Electron apps on macOS. Designed to integrate seamlessly with the hardware notch on modern MacBooks, providing a native-like experience.

Table of Contents

Features

CleanShot 2025-12-07 at 23 45 47

  • 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-island

Usage

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.

Built-in Icons Preview

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 iconWidth and iconHeight for 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.

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

  1. Detection: The library checks if the app is running on a compatible MacBook (M1 Pro/Max, M2/M3 Air/Pro) using system model identifiers.
  2. Positioning: It calculates the screen center and aligns the notification window directly under the hardware notch.
  3. 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.
  4. 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.

  1. Basic Example: Shows a custom icon (AirPods) with dynamic sizing.

    npm start
    
  2. Advanced Example: Demonstrates sequential updates (Loading -> Success) and event triggering.

    npm run example:advanced
    

License

MIT

About

A highly customizable 'Dynamic Island' style notification system for Electron apps on macOS.

Resources

License

Stars

Watchers

Forks