Nib provides full access to macOS system notifications, including instant push, scheduled delivery, custom sounds, action buttons, and text input.
Quick Notification¶
The simplest way to send a notification is app.notify():
import nib
def main(app: nib.App):
app.title = "Notify"
app.icon = nib.SFSymbol("bell")
app.width = 200
app.height = 100
app.build(
nib.Button("Send Notification", action=lambda: app.notify("Hello!", "World"))
)
nib.run(main)
app.notify() parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
title |
str |
required | Main notification title |
body |
str |
None |
Body text |
subtitle |
str |
None |
Secondary line below the title |
sound |
bool |
True |
Play the default notification sound |
identifier |
str |
None |
Unique ID (for updating or canceling) |
app.notify(
"Download Complete",
body="report.pdf has been saved to Downloads",
subtitle="File Manager",
sound=True,
)
Requesting Permission¶
macOS requires user permission before delivering notifications. Request it early:
def on_permission(granted):
if granted:
print("Notifications enabled")
else:
print("Notifications denied")
app.notifications.request_permission(callback=on_permission)
Note
The first call shows the system permission dialog. Subsequent calls return the cached result. If the user denied permission, they must re-enable it in System Settings.
Notification Objects¶
For full control, create Notification objects and push them through the notification manager:
from nib import Notification
notification = Notification(
title="New Message",
body="You have a new message from Alice",
subtitle="Messages",
)
app.notifications.push(notification)
Custom sounds¶
Use NotificationSound to configure the notification sound:
from nib import Notification, NotificationSound, NotificationSoundName
# Default sound
notification = Notification(
title="Alert",
body="Something happened",
sound=NotificationSound(name=NotificationSoundName.DEFAULT),
)
# Built-in macOS sound by name
notification = Notification(
title="Alert",
body="Something happened",
sound=NotificationSound(name=NotificationSoundName.custom("Glass")),
)
# Other built-in sounds: "Frog", "Ping", "Pop", "Purr", "Sosumi", "Tink"
Scheduling Notifications¶
Schedule at a specific time¶
from datetime import datetime, timedelta
from nib import Notification
reminder = Notification(
title="Break Time",
body="You have been working for 2 hours. Take a break!",
)
# Schedule for 1 hour from now
notification_id = app.notifications.schedule(
reminder,
at=datetime.now() + timedelta(hours=1),
)
Schedule daily recurring¶
from nib import Notification
daily_reminder = Notification(
title="Daily Standup",
body="Time for the morning standup meeting",
)
# Every day at 9:00 AM
app.notifications.schedule_daily(
daily_reminder,
from_time="09:00",
)
# Multiple times per day between 8 AM and 6 PM
app.notifications.schedule_daily(
daily_reminder,
from_time="08:00",
to_time="18:00",
count=3,
)
# Daily for one week
from datetime import date, timedelta
app.notifications.schedule_daily(
daily_reminder,
from_time="09:00",
from_date=date.today(),
to_date=date.today() + timedelta(days=7),
)
Reschedule¶
Cancel and reschedule an existing notification:
app.notifications.reschedule(
reminder,
at=datetime.now() + timedelta(hours=2),
)
Notification Actions¶
Actions appear as buttons on the notification. Users can tap them to trigger callbacks without opening the app.
from nib import Notification, NotificationAction, TextInputNotificationAction
notification = Notification(
title="New Message",
body="Alice: Hey, are you free for lunch?",
actions=[
NotificationAction(
id="reply",
title="Reply",
text_input=TextInputNotificationAction(
button_title="Send",
placeholder="Type your reply...",
),
),
NotificationAction(
id="mark_read",
title="Mark as Read",
),
],
)
app.notifications.push(notification)
Handling action callbacks¶
Register a handler to receive action events:
def handle_action(notification_id, action_id, user_text):
if action_id == "reply" and user_text:
print(f"User replied: {user_text}")
elif action_id == "mark_read":
print("Marked as read")
unsubscribe = app.notifications.on_action(handle_action)
# Later, to stop receiving callbacks:
# unsubscribe()
The callback receives three arguments:
| Argument | Type | Description |
|---|---|---|
notification_id |
str |
ID of the notification that was acted on |
action_id |
str |
ID of the action button that was tapped |
user_text |
str or None |
Text entered if the action has text_input |
Action options¶
from nib import NotificationAction, NotificationActionOption
NotificationAction(
id="delete",
title="Delete",
options=[
NotificationActionOption.DESTRUCTIVE, # Red button
NotificationActionOption.AUTHENTICATION_REQUIRED, # Requires unlock
],
)
NotificationAction(
id="open",
title="Open App",
options=[NotificationActionOption.FOREGROUND], # Brings app to front
)
Canceling Notifications¶
# Cancel a specific notification by ID
app.notifications.cancel_notification(notification_id)
# Cancel all scheduled and delivered notifications
app.notifications.cancel_all_notifications()
Querying Notifications¶
Retrieve notifications that are currently scheduled or displayed in Notification Center:
# Get all scheduled (pending) notifications
def on_scheduled(notifications):
for n in notifications:
print(f"Scheduled: {n.title} (id: {n.id})")
app.notifications.get_all_scheduled_notifications(on_scheduled)
# Get all delivered (visible in Notification Center) notifications
def on_delivered(notifications):
print(f"{len(notifications)} notifications showing")
app.notifications.get_all_delivered_notifications(on_delivered)
# Get a specific notification by ID
app.notifications.get_scheduled_notification(
"some-id",
callback=lambda n: print(f"Found: {n.title}") if n else print("Not found"),
)
Full Example¶
A complete notification demo app:
import nib
from datetime import datetime, timedelta
from nib import Notification, NotificationAction, NotificationSound, NotificationSoundName, TextInputNotificationAction
def main(app: nib.App):
app.title = "Notifications"
app.icon = nib.SFSymbol("bell.badge")
app.width = 300
app.height = 350
# Request permission on startup
app.notifications.request_permission()
# Handle action callbacks
response_label = nib.Text("No response yet",
foreground_color=nib.Color.SECONDARY,
font=nib.Font.CAPTION)
def on_action(notif_id, action_id, user_text):
if action_id == "reply" and user_text:
response_label.content = f"Reply: {user_text}"
elif action_id == "dismiss":
response_label.content = "Dismissed"
app.notifications.on_action(on_action)
def send_simple():
app.notify("Hello!", body="This is a simple notification.")
def send_with_actions():
notification = Notification(
title="Message from Alice",
body="Want to grab coffee?",
sound=NotificationSound(name=NotificationSoundName.custom("Glass")),
actions=[
NotificationAction(
id="reply",
title="Reply",
text_input=TextInputNotificationAction(
button_title="Send",
placeholder="Type reply...",
),
),
NotificationAction(id="dismiss", title="Dismiss"),
],
)
app.notifications.push(notification)
def schedule_reminder():
reminder = Notification(
title="Reminder",
body="This was scheduled 10 seconds ago.",
)
app.notifications.schedule(reminder, at=datetime.now() + timedelta(seconds=10))
response_label.content = "Reminder scheduled in 10s"
def cancel_all():
app.notifications.cancel_all_notifications()
response_label.content = "All notifications canceled"
app.build(
nib.VStack(
controls=[
nib.Text("Notification Demo", font=nib.Font.HEADLINE),
nib.Divider(),
nib.Button("Simple Notification", action=send_simple),
nib.Button("With Actions", action=send_with_actions),
nib.Button("Schedule (10s)", action=schedule_reminder),
nib.Button("Cancel All", action=cancel_all),
nib.Divider(),
nib.Text("Last response:", font=nib.Font.CAPTION),
response_label,
],
spacing=8,
padding=16,
)
)
nib.run(main)