Files
ilovetui/notification

notification

Toast-style notifications, triggered from anywhere in a bubbletea program via an exported tea.Msg (ShowMsg/Show) rather than a direct reference to the Model that ends up rendering them - standard Elm architecture, no IPC between processes.

It composites over an already-rendered string, so it has no dependency on github.com/anotherhadi/ilovetui/layout: the same Model works whether the host uses layout for its main content or not.

Quick start

import (
	"github.com/anotherhadi/ilovetui/notification"
)

type model struct {
	notif         notification.Model
	width, height int
}

func newModel() model {
	return model{notif: notification.New()}
}

func (m model) Init() tea.Cmd { return m.notif.Init() }

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.KeyPressMsg:
		if msg.String() == "s" {
			return m, notification.Show("Saved", "Config written to disk", notification.Success)
		}
	}

	var cmd tea.Cmd
	m.notif, cmd = m.notif.Update(msg)
	return m, cmd
}

func (m model) View() tea.View {
	background := renderYourUI(m.width, m.height)
	view := tea.NewView(m.notif.Render(background))
	view.AltScreen = true
	return view
}

Any component in the same bubbletea program can trigger a toast via notification.Show, without holding a reference to the notification.Model that will actually render it - that Model just needs to see every tea.Msg the program produces (i.e. get its Update called from the top-level Update), same as any other child model.

Showing and dismissing

return m, notification.Show("Saved", "Config written to disk", notification.Success)

return m, notification.Show("Sticky", "Stays until dismissed",
	notification.Info, notification.WithID("sticky-demo"), notification.WithDuration(0))
return m, notification.Dismiss("sticky-demo")

Four kinds: Info, Success, Warning, Error, each with its own color preset (see Styling below). By default a toast auto-dismisses after notification.DefaultDuration (3s); WithDuration(0) makes it sticky - it stays until Dismiss(id) removes it, so a sticky toast needs WithID to be dismissable later (an auto-generated id is never returned to the caller). Showing again with the same id replaces the toast in place, resetting its position and timer, instead of stacking a duplicate.

Position and stacking

n := notification.New(notification.WithPosition(notification.TopRight))

Six anchors: Top, TopLeft, TopRight, Bottom, BottomLeft, BottomRight - toasts always hug an edge or corner, never the middle of the screen. Multiple toasts stack along the anchored edge, newest closest to it; a stack that overflows the background's height clips the oldest toasts first, so the newest ones stay visible.

Styling

n := notification.New(notification.WithMaxWidth(40), notification.WithStyles(myStyles))

return m, notification.Show("Title", "Message", notification.Success,
	notification.WithToastStyle(oneOffStyle))

WithMaxWidth caps how wide a toast box can grow before its message wraps; a toast narrower than the cap shrinks to fit its content instead of padding out to it. A toast can also never overflow past the edge of whatever background it's rendered on, regardless of this cap. WithStyles sets the default per-Kind look for every toast shown by this Model; WithToastStyle (a Show option) overrides it for one toast alone. DefaultStyles() builds from style.S: Info uses Primary (no dedicated "info" color in the theme), Success/Warning/Error use their matching style.S alias.

Examples

  • examples/notification - all four kinds, a sticky toast with manual dismiss, cycling through all six positions.