Push notification

Push notification shows how a notification looks on iPhone and Android, a Live Activity on the iPhone Lock Screen, and the phone they appear on. Use it for previews, notification settings and documentation.

Designed in code from the owner's two references (a ride Live Activity and a countdown Live Activity) and reviewed on real device sizes. Beta until the Android 16 Live Update behaviour is confirmed against the oldest Android version we support, and the final Nuton app icon replaces the placeholder. Everything on this page is implemented and tested.

Preview

Switch the platform, appearance, expanded state and copy length. Tap the notification or an action to see what it would do.

Tap the notification or an action

tsx
import { PushNotification } from '@nuton/ui'

<PushNotification
  title="Section 3 is ready"
  body="Pick up Form and Function where you left off. About 8 minutes."
  time="7:30 pm"
  actions={['Start section', 'Tomorrow']}
  onOpen={openSection}
  onAction={runAction}
/>

Installation

Availability

@nuton/ui and @nuton/tokens are private workspace packages and aren't published yet (D4, D12). Inside the Nuton monorepo, add them as workspace dependencies.

bash
pnpm add @nuton/ui @nuton/tokens @remixicon/react
tsx
import '@nuton/tokens/tokens.css'
import '@nuton/ui/styles.css'

Usage

tsx
import { Nuri, PushNotification } from '@nuton/ui'

<PushNotification
  title="Section 3 is ready"
  body="Pick up Form and Function where you left off. About 8 minutes."
  time="7:30 pm"
  onOpen={() => router.push('/lessons/form-and-function/3')}
/>

What this is, and is not

It draws a notification the way the operating system does, so designers, reviewers and the settings screens can see exactly what people will receive. It does not send, schedule or register anything: push delivery, Live Activities and Android's ongoing notifications are built in the native apps and the notification service.

  • Write for the system surface: a short title, one line, and (for a badge, a milestone or a welcome back) a small art image. Nothing is tinted or restyled, so people recognise it as a notification.
  • The app icon is the identity. Use art sparingly: Nuri beside a reward or a welcome, never on every notification.
  • Set appearance to the phone's mode, not the app's theme. A phone in dark mode shows dark notifications whatever the app is set to.
  • Use platform="android" for Android. It is a different design (app row, one-line body, actions on the notification), not the iPhone one redrawn.
  • Use LiveActivity only when the time left or the progress is itself the message: a streak countdown, a lesson in progress. A reminder, a badge or a recap stays an ordinary notification.
  • Put notifications inside a PushPhone to judge them where people see them: the iPhone Lock Screen, another app, or the Android shade, at the device's real proportions. Notifications inside follow its platform and appearance.
  • Scale it by setting --nu-push-u (one point) on an ancestor. The default is one CSS pixel, which is the real device size.

Variants and sizes

Sizes are real device sizes: iOS points and Android dp, on an iPhone 16 (393 × 852 pt) and a Pixel (412 × 915 dp). The previews are scaled to fit this column.

iPhone

26 radius, 14 padding, a 38 app icon (9 radius). The title is 15 semibold, up to two lines; the line is 15/20, up to three; the time is 13. Expanded (touch and hold) is a 28 radius card with a 30 icon and 17/22 text, and the actions sit in their own 22 radius list.

12 cards are ready12:10 pmA 3-minute review keeps what you learned fresh.
Banner and Lock Screen
New badge: First Flashcard Session4:41 pmYou finished your first review. See it in your collection.
With a small Nuri (a badge, a milestone, a welcome back)
Nuton7:30 pm
Section 3 is readyPick up Form and Function where you left off. About 8 minutes.
Expanded, with actions (touch and hold)
12 cards are ready12:10 pmA 3-minute review keeps what you learned fresh.
Dark appearance

Android

28 radius (6 where grouped), 16 and 18 padding, a 20 app icon in the app row, a 48 image (12 radius). The title is 16 medium on one line; the line is 14/20, one line collapsed and up to four expanded. Actions are 36 tall pills. A group is a shared header with the corners rounded as the system rounds them.

Nuton12:10 pm
12 cards are readyA 3-minute review keeps what you learned fresh.
Collapsed: one line
Nuton7:30 pm
Section 3 is readyPick up Form and Function where you left off. About 8 minutes.
Expanded: the whole line, and actions
Nuton12:10 pm
12 cards are readyA 3-minute review keeps what you learned fresh.
Nuton4:41 pm
New badge: First Flashcard SessionYou finished your first review.
NutonMon 9:00 am
Welcome back whenever you’re readyYour next section takes about 8 minutes.
Grouped under one Nuton header
Nuton12:10 pm
12 cards are readyA 3-minute review keeps what you learned fresh.
Dark appearance

Live Activity

28 radius, dark in either appearance, as on iOS. The title is the one large element (50 bold; 38 for a success state like “Streak safe”; 30 with a track), never wraps and is cut with an ellipsis rather than overlap the icon; the note is one short sentence (17/22); the app icon is 76 (56 beside a track). status turns the title red for the last hour or green once the goal is met.

122:00:45Your 12-day streak is still within reach.
Streak, with time left
120:50:01One section is enough.
Streak, last hour (status urgent)
12Streak safeSee you tomorrow.
Streak safe (status success)
6 min leftForm and Function
3 of 6
Lesson in progress (with progress)

Phone

The device and the system surface around a notification: the iPhone Lock Screen (date, clock, flashlight and camera), another app being used (where a banner drops in), the touch-and-hold backdrop, and the Android notification shade with quick settings. It fills its container, keeps the device's real proportions and scales everything in it, so a notification inside is the size it is on the device. The status bar, wallpaper and quick settings are decoration, hidden from assistive technology; what you put inside is real.

122:00:45Your 12-day streak is still within reach.
iPhone Lock Screen
iPhone, another app (screen="app")
Nuton7:30 pm
Section 3 is readyPick up Form and Function where you left off.
iPhone, touch and hold (dimmed)
Nuton12:10 pm
12 cards are readyA 3-minute review keeps what you learned fresh.
Nuton4:41 pm
New badge: First Flashcard SessionYou finished your first review.
NutonMon 9:00 am
Welcome back whenever you’re readyYour next section takes about 8 minutes.
Android notification shade

Phone sizes

Three sizes per platform, in the device's own points (iPhone) or dp (Android), for checking how a notification truncates and wraps: iPhone 375 × 812, 393 × 852 (the iPhone 16) and 440 × 956; Android 360 × 800, 412 × 915 (a Pixel) and 600 × 960. The previews below are drawn at one width on the page, so the larger devices show the same notification smaller, as they would on a screen.

12 cards are ready12:10 pmA 3-minute review keeps what you learned fresh.
iPhone compact 375 × 812
12 cards are ready12:10 pmA 3-minute review keeps what you learned fresh.
iPhone 16 393 × 852
12 cards are ready12:10 pmA 3-minute review keeps what you learned fresh.
iPhone large 440 × 956
Nuton12:10 pm
12 cards are readyA 3-minute review keeps what you learned fresh.
Android compact 360 × 800
Nuton12:10 pm
12 cards are readyA 3-minute review keeps what you learned fresh.
Pixel 412 × 915
Nuton12:10 pm
12 cards are readyA 3-minute review keeps what you learned fresh.
Android large 600 × 960

Colours and contrast

These are the system's surfaces, so they use the colour primitives and follow the phone's appearance, not the Nuton theme. Contrast was computed from the token values, against the lightest background each can sit on.

PairContrast
iOS light: title and line · secondary text17.18:1 · 4.61:1
iOS dark: title and line · secondary text15.72:1 · 6.84:1
Android light: secondary text · action4.67:1 · 5.79:1
Android dark: secondary text · action7.25:1 · 6.60:1
Live Activity: title · note · urgent · success13.77:1 · 7.25:1 · 4.91:1 · 8.02:1

The tests run axe in jsdom, which cannot check contrast, so the figures above are computed, not measured in a browser.

States and interactions

StaticnowNot tappable without onOpen.
StaticA plain notification for previews. It is not a button.
TappableWith onOpen it is a button with a 2px primary focus ring.

When the copy is long

SurfaceTitleLine
iOS banner and Lock ScreenUp to two lines, then an ellipsisUp to three lines, then an ellipsis
iOS expandedFullFull
Android collapsedOne line, ellipsisOne line, ellipsis
Android expandedOne line, ellipsisUp to four lines, then an ellipsis
Live ActivityNever wrapsThe note wraps

State tokens

Set on .nu-push and .nu-live: --nu-push-u (one point; scale the whole notification), --nu-push-text, --nu-push-sub, --nu-push-surface, --nu-push-solid, --nu-push-hair, --nu-push-chip and --nu-push-accent. The dark values are set by data-appearance="dark".

Decisions still open

  • The app icon is a placeholder: Nuri's head on a light tile. It should be replaced by the Nuton app icon through icon.
  • Android 16 has a purpose-built Live Update for an ongoing countdown. Earlier versions use an ordinary ongoing notification with a chronometer. Confirm against the oldest Android version we support before building it.
  • Glass (the blur behind a notification) needs a real browser; there is a solid fallback where backdrop-filter is not supported, which has not been checked in Safari.

Composition examples

A stack that opens

The component draws one notification. A stack, Show Less and Clear are the screen's own controls, and each notification reports its tap with onOpen.

Tap the notification to open the stack

The full set of Nuton learning notifications, drawn on both platforms with the rules for when each is sent, is in the notification review at /design-exploration/notifications/.

API reference

Props

PushNotification props
PropTypeDefaultDescription
platform'ios' | 'android''ios'Which system to draw. They are different designs.
appearance'light' | 'dark''light'The phone's appearance, not the app theme.
titlerequiredstring—Short and specific. iOS: up to two lines. Android: one line.
bodyrequiredstring—One supporting line. iOS: up to three lines. Android: one line, or four when expanded.
timestring'now'"now", "7:30 pm", "Mon 9:00 am".
appNamestring'Nuton'Shown on Android and in the expanded iOS view.
iconReactNode—The app icon. Defaults to the Nuton icon (a placeholder).
artReactNode—A small image on the right. Leave it out for everything but a badge, a milestone and a welcome back.
actionsreadonly string[]—Up to two short verbs, shown when expanded.
expandedbooleanfalseiOS: the touch-and-hold view with actions beneath. Android: the whole line and the actions.
onOpen() => void—Makes the notification a button that opens its destination.
onAction(index: number, label: string) => void—Called when an action is pressed.
progressnumber—Android: 0 to 1. Shows a progress bar under the text, for an ongoing countdown.
onDismiss() => void—iOS expanded: shows the close button. Called when it is pressed.
dismissLabelstring'Dismiss'Accessible name of the iOS close button.
onToggle() => void—Android: shows the expand chevron. Ignored on iOS.
toggleLabelstring—Accessible name of the chevron. Defaults to Expand or Collapse.
group'first' | 'middle' | 'last'—Android: the position in a group, to round the shared corners.
…section propsHTMLAttributes<HTMLElement>—className, id and other attributes go to the container. The ref points at it.
PushPhone props
PropTypeDefaultDescription
labelrequiredstring—Names the phone for screen readers, such as "iOS Lock Screen, grouped".
platform'ios' | 'android''ios'iPhone or Android.
appearance'light' | 'dark''light'The phone's mode. Notifications inside follow it unless they say otherwise.
size'compact' | 'standard' | 'large''standard'iPhone 375 × 812, 393 × 852, 440 × 956. Android 360 × 800, 412 × 915, 600 × 960.
screen'lock' | 'app''lock'iPhone only. The Lock Screen, or another app where a banner drops in. Android always shows the shade.
dimmedbooleanfalseiPhone Lock Screen: the touch-and-hold backdrop, with what is inside placed lower.
timestring'9:41'The clock.
datestring—The date line. Defaults to "Thursday 22 October" (iPhone) or "Thu, 22 Oct" (Android).
childrenReactNode—The notifications, Live Activities and any controls the screen needs.
…div propsHTMLAttributes<HTMLDivElement>—className, id and other attributes go to the outer container. The ref points at it.
LiveActivity props
PropTypeDefaultDescription
titlerequiredstring—The answer, large: a countdown, a state or a time left. It never wraps.
notestring—One short supporting sentence.
labelstring—A small line above the title, such as a count.
labelIconReactNode—Icon beside the label.
iconReactNode—The app icon on the right. Defaults to the Nuton icon.
status'default' | 'urgent' | 'success''default'urgent turns the title red (the last hour); success turns it green.
progressnumber—0 to 1. Adds a track with a marker, for something that progresses.
markerReactNode—Small artwork riding the track.
chipstring—A short count at the end of the top row in the track layout, such as "3 of 6".
aria-labelstring'Live Activity'Accessible name of the group.

Accessibility

Keyboard

KeyBehaviour
TabMoves to the notification (with onOpen), then the actions, then the Android chevron
Enter / SpaceOpens the notification or runs the focused action

Screen readers

A tappable notification is one button named by its title and line. The app icon and the small image are hidden. A Live Activity is a labelled group; its title is not a live region, so a ticking countdown is not announced every second. The track is a progressbar with its value.

Focus

A PushPhone is a labelled group; its status bar, clock, wallpaper and quick settings are hidden, so only what you put inside is read. Every button has a 2px primary ring. The Android chevron reports aria-expanded.

Contrast and colour

Every text pair is 4.5:1 or more against the lightest background it can sit on (table above). Urgency is never colour alone: the last-hour Live Activity also changes its words.

Touch and pointer

This component previews what the system draws; the real notification's touch targets are the system's. In the preview the Android chevron is 24 × 24 and the actions are 36 tall.

You must: keep the title and line meaningful on their own, because the system may truncate them, and make sure the destination exists when the notification is tapped.

Figma mapping

There is no Figma frame for this component. It was designed in code from the owner's two references and reviewed on device sizes; Figma stays read-only.

Guidelines and related components

Use Push notification for

  • Previewing a notification before it is built
  • Notification settings screens that show what each kind looks like
  • Documentation and design review

Don't use it for

  • A message inside the app (use Alert or Toast)
  • Sending or scheduling anything (that is the notification service)
  • A custom, branded card on the lock screen: the system draws the real one

Do and don't

12 cards are ready12:10 pmA 3-minute review keeps what you learned fresh.
DoSay what and what next, in one line The title and line still make sense if the system truncates them.
You’re about to lose everything!9:59 pmYour streak dies tonight unless you come back right now.
Don'tPressure the learner Guilt and countdown threats make people turn notifications off.
7 days in a row8:02 pmYou’ve learned something every day this week.
DoKeep Nuri for rewards and welcomes The app icon identifies Nuton; a small Nuri adds warmth where it fits.
12 cards are ready12:10 pmA 3-minute review keeps what you learned fresh.
Don'tPut art on every notification It stops being special and crowds the text.