Toast & SvgSource

Two small globals: toast shows a short message over the app for about two seconds, and SvgSource turns SVG markup into an image source. A toast is a banner inside the running app, not an OS notification: a notification in the system tray is a remote push (Push), and local or scheduled notifications do not exist (Not available).

  • show a short "Saved" or "Copied" message after an action (toast)
  • tell the user a request failed without opening a dialog
  • show SVG markup as an image or an icon (SvgSource)
  • recolor an SVG icon, or tint it with a theme color
  • show an SVG file of the project as an image (asset('./logo.svg'))

Show a short message: toast

TypeScript
toast(message: string): void

toast shows a short message ("Saved", "Copied", "No connection") near the bottom of the screen for about 2 seconds, then hides it. It is fire-and-forget: nothing to await, no tap handler, no way to close it early. It is drawn inside the app while the app is open, never in the system's notification tray; for that, see Push.

TypeScript
const code = 'LC-4821'

UIScreen(
  UIColumn(
    UIText(`Invite code: ${code}`),
    UIButton(UIText('Copy')).onClick(() => {
      clipboard.write(code)
      toast('Copied')
    }),
  ).style({ p: 16, gap: 12 }),
).open()
Note

keep it to a few words: the desktop host draws the message on one line, unwrapped. Hosts differ on a second toast while one is showing (queued on iOS and Android, stacked on the web, replaced on desktop), so show one at a time.

Note

a toast is not an element of the UI tree, so a test flow (lecodes render / lecodes test) cannot expect its text on any host, and headless runs do not draw it. console.log the outcome the test should check.

Report a failed request: toast in a catch

A toast tells the user that something did not work without stopping them with a dialog. Check the HTTP status (an error status still resolves) and catch the rejection of a request that never reached the server:

TypeScript
const status = UIText('No news yet')

const reload = async () => {
  try {
    const res = await fetch('https://api.example.com/news')
    if (res.status !== 200) { toast(`Could not load the news (${res.status})`); return }
    status.text = `${res.json<unknown[]>().length} stories`
  } catch (e) {
    toast('No connection')
  }
}

UIScreen(
  UIColumn(status, UIButton(UIText('Reload')).onClick(reload)).style({ p: 16, gap: 12 }),
).open()

For an error the user must act on, use a dialog (UIModal, widget.md).

SVG markup as an image: SvgSource

TypeScript
SvgSource(svg: string): SvgSource     // { readonly svg: string, tintColor: ColorInput | null }

SvgSource wraps a string of SVG XML so it can be shown as an image: the source of a UIImage or a bgImage style (UI content). SvgSource is also the type: what assetIcon("lucide:bell") and asset('./logo.svg') return too, so a component that takes an icon is typed (icon: SvgSource). For an icon from a pack, assetIcon needs no hand-written SVG (Icons).

TypeScript
const star = SvgSource(
  '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">' +
  '<path fill="#f5a623" d="M12 2l3 7h7l-5.5 4.5 2 7.5-6.5-4.5-6.5 4.5 2-7.5L2 9h7z"/></svg>',
)

UIScreen(
  UIRow(
    UIImage(star).style({ width: 24, height: 24 }),
    UIText('Favorites'),
  ).style({ p: 16, gap: 8, alignItems: 'center' }),
).open()

Change the color of an SVG icon: tintColor

Set tintColor on an SvgSource to change the color of an SVG icon: the whole SVG is drawn in that one color, every fill and stroke, which suits a one-color icon, not multicolored artwork. It takes any color form or a theme variable (var(--…), an accessor of theme()), which follows later theme() calls; null (the default) keeps the SVG's own colors. The tintColor style of a UIImage does the same for one image and wins over the source's tint.

The tint is read when the image takes the source: set tintColor before UIImage(icon), and to change it later, set it and assign the source again (img.src = icon).

TypeScript
const colors = theme({ liked: '#e0245e', idle: '#9aa0a6' })
const heart = SvgSource('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">' +
  '<path d="M12 21 3.5 12.5a5 5 0 0 1 8.5-6 5 5 0 0 1 8.5 6z"/></svg>')
heart.tintColor = colors.idle             // set before the image takes the source

let liked = false
const icon = UIImage(heart).style({ width: 28, height: 28 }).named('heart')

UIScreen(
  UIButton(icon).named('like').onClick(() => {
    liked = !liked
    heart.tintColor = liked ? colors.liked : colors.idle
    icon.src = heart                      // the tint is read on assign: assign the source again
  }),
).open()
Note

as a bgImage, an SvgSource keeps its own colors: the value's tintColor is applied to a UIImage only.

An SVG file of the project: asset('./logo.svg')

asset('./logo.svg') reads the file at build time and gives the same SvgSource value, ready for UIImage or bgImage. import logo from './logo.svg' bundles to the same value, but the project's types do not declare .svg modules and tsc rejects it: use asset().

TypeScript
const logo = asset('./logo.svg')         // an SvgSource, made from the file at build time

UIScreen(
  UIColumn(
    UIImage(logo).style({ width: 120, height: 40, tintColor: '#ffffff' }),
  ).style({ p: 24, bgColor: '#15a34a', alignItems: 'center' }),
).open()
Note

every asset('./logo.svg') in the app is the SAME object. Setting its tintColor changes it for every image that takes it afterwards; tint one image with the tintColor style (as above), or tint a copy: { ...logo, tintColor: '#ffffff' }.

See also

  • UI content: UIImage, assetIcon and the full set of image sources
  • Styling: bgImage and the other background layers
  • Push: notifications that reach the phone when the app is closed
  • Networking: FetchResponse / File as the other image sources