floatnote
Transparent always-on-top drawing and note-taking overlay for macOS with annotation tools.
Preview
Key Features
Draw Anywhere
Freehand drawing on top of any application. Circles, arrows, text — whatever you need.
Click-Through
The overlay is invisible until you need it. Cmd+Shift+D toggles draw mode.
Screenshot + Annotations
Capture your screen WITH your drawings. Perfect for bug reports.
Precision Tools
Straight lines with Shift, shapes for highlighting, text for callouts.
Color Picker
Quick color palette appears when you need it, disappears when you don't.
macOS Native
Built with Electron, feels like a native app. Lives in your menu bar.
I was on a video call, trying to explain a UI bug. "No, not that button, the one below it. No, the OTHER one below it. The one that says... no, scroll up... no, too far..."
This happens constantly. Whether it's pair programming, design reviews, or debugging sessions, pointing at things on a shared screen is HARD.
That's when I wished I could just... draw on my screen.
The Idea
An invisible overlay that sits on top of everything. Always on top. When you need to annotate something, you draw on it. When you're done, it disappears.
Like one of those football telestrators, but for your Mac.
Building With Electron (Again)
I know, I know, Electron apps are big. But for this use case, it's perfect. The app is a transparent, frameless, always-on-top window that covers your entire screen. You can click through it when you're not drawing, and it captures input when you are.
The drawing itself is just canvas operations:
- •Freehand drawing with variable stroke width
- •Straight lines (hold Shift)
- •Circles and rectangles (for highlighting)
- •Text annotations
- •An eraser
Colors are picked from a floating palette that appears when you hit a hotkey.
The Transparency Trick
Making an Electron window transparent is easy. Making it CLICK-THROUGH is harder.
The solution is setIgnoreMouseEvents(true, { forward: true }). This tells the window to ignore mouse events but forward them to whatever is below. When you want to draw, you toggle this off.
The toggle is a global hot...
Built With
Impact
npm Downloads
475+
Platform
macOS
Under the Hood
A peek at the implementation — the kind of code that powers floatnote.
1// The transparency + click-through dance
2// This is the magic that makes floatnote work
3
4import { BrowserWindow, globalShortcut, screen } from 'electron';
5
6let overlayWindow: BrowserWindow | null = null;
7let isDrawMode = false;
8
9function createOverlay() {
10 const { width, height } = screen.getPrimaryDisplay().workAreaSize;
11
12 overlayWindow = new BrowserWindow({
13 width,
14 height,
15 x: 0,
16 y: 0,
17 transparent: true, // See-through
18 frame: false, // No window chrome
19 alwaysOnTop: true, // Always visible
20 skipTaskbar: true, // Don't show in dock
21 hasShadow: false, // No shadow (it's invisible!)
22 webPreferences: {
23 nodeIntegration: true,
24 contextIsolation: false,
25 },
26 });
27
28 // Start in click-through mode
29 overlayWindow.setIgnoreMouseEvents(true, { forward: true });
30
31 overlayWindow.loadFile('overlay.html');
32
33 // Global hotkey to toggle draw mode
34 globalShortcut.register('CommandOrControl+Shift+D', toggleDrawMode);
35}
36
37function toggleDrawMode() {
38 isDrawMode = !isDrawMode;
39
40 if (isDrawMode) {
41 // Capture mouse events - we're drawing now
42 overlayWindow?.setIgnoreMouseEvents(false);
43 overlayWindow?.webContents.send('draw-mode', true);
44
45 // Show the toolbar
46 overlayWindow?.webContents.send('show-toolbar');
47 } else {
48 // Click-through mode - window is invisible again
49 overlayWindow?.setIgnoreMouseEvents(true, { forward: true });
50 overlayWindow?.webContents.send('draw-mode', false);
51 }
52}
53
54// Screenshot with annotations
55async function captureWithAnnotations(): Promise<Buffer> {
56 // Temporarily hide the toolbar
57 overlayWindow?.webContents.send('hide-toolbar');
58
59 await sleep(100); // Wait for render
60
61 // Capture the SCREEN (not the window)
62 const sources = await desktopCapturer.getSources({
63 types: ['screen'],
64 thumbnailSize: screen.getPrimaryDisplay().size
65 });
66
67 const screenCapture = sources[0].thumbnail;
68
69 // Now capture our overlay
70 const overlayCapture = await overlayWindow?.webContents.capturePage();
71
72 // Composite them together
73 const combined = await compositeImages(screenCapture, overlayCapture);
74
75 overlayWindow?.webContents.send('show-toolbar');
76
77 return combined.toPNG();
78}