Skip to main content

Toast

Show temporary status messages through solid-toaster integration.

View as MarkdownUpstream

Use solid-toaster for temporary status messages. It is a separate package; Moraine does not export a Toast component. Mount its host once and use Moraine icons and theme variables to match your application.

Basic usage#

import 'solid-toaster/style.css'

import { Button, Icon } from 'moraine'
import { BaseToaster, toast } from 'solid-toaster'

export default function App() {
  return (
    <>
      <Button onClick={() => toast.success('Changes saved!')}>Save</Button>
      <BaseToaster
        visibleToasts={4}
        preventDuplicate
        style={{
          '--normal-bg': 'var(--popover)',
          '--normal-text': 'var(--popover-foreground)',
          '--normal-border': 'var(--border)',
          '--border-radius': 'var(--radius)',
        }}
        icons={{
          success: () => <Icon name="icon-success" />,
          error: () => <Icon name="icon-error" />,
          warning: () => <Icon name="icon-warning" />,
          info: () => <Icon name="icon-info" />,
          loading: () => <Icon name="icon-loading" class="animate-spin" />,
          close: () => <Icon name="icon-close" />,
        }}
      />
    </>
  )
}

Playground#

    Props

    Usage#

    Install and mount the host#

    Install solid-toaster:

    bun add solid-toaster

    Import solid-toaster/style.css and mount one BaseToaster at your application root before calling toast methods. Its icons and style props connect the host to Moraine’s semantic icons and theme variables, as shown in Basic usage.

    import { Button, Icon } from 'moraine'
    import { BaseToaster, toast } from 'solid-toaster'
    
    export function SetupUsage() {
      const ICONS = {
        success: () => <Icon name="icon-success" />,
        error: () => <Icon name="icon-error" />,
        warning: () => <Icon name="icon-warning" />,
        info: () => <Icon name="icon-info" />,
        loading: () => <Icon name="icon-loading" class="animate-spin" />,
        close: () => <Icon name="icon-close" />,
      }
    
      const TOASTER_STYLE = {
        '--normal-bg': 'var(--popover)',
        '--normal-text': 'var(--popover-foreground)',
        '--normal-border': 'var(--border)',
        '--border-radius': 'var(--radius)',
      }
    
      return (
        <>
          <BaseToaster style={TOASTER_STYLE} visibleToasts={4} icons={ICONS} />
          <BaseToaster id="custom" position="bottom-left" style={TOASTER_STYLE} icons={ICONS} />
          <Button onClick={() => toast.success('Changes saved!')}>Trigger Success Toast</Button>
        </>
      )
    }

    Calls and lifecycles#

    Use semantic toast calls for immediate feedback and toast.promise for a promise whose pending, success, and error states need distinct messages. Duration and scoped instances are supplied to the toaster integration.

    import { Button } from 'moraine'
    import { toast } from 'solid-toaster'
    
    export function CallsUsage() {
      return (
        <div class="flex flex-wrap gap-3 items-center">
          <Button variant="outline" onClick={() => toast.success('Changes saved successfully!')}>
            Success
          </Button>
          <Button
            variant="outline"
            onClick={() => toast.error('Failed to update repository settings.')}
          >
            Error
          </Button>
          <Button variant="outline" onClick={() => toast.warning('Low storage remaining on volume.')}>
            Warning
          </Button>
        </div>
      )
    }

    Duplicate messages#

    Set preventDuplicate on the host to avoid showing repeated toasts with the same content.

    import { Button, Icon } from 'moraine'
    import { BaseToaster, toast } from 'solid-toaster'
    
    export function PrevientDuplicate() {
      const ICONS = {
        success: () => <Icon name="icon-success" />,
        error: () => <Icon name="icon-error" />,
        warning: () => <Icon name="icon-warning" />,
        info: () => <Icon name="icon-info" />,
        loading: () => <Icon name="icon-loading" class="animate-spin" />,
        close: () => <Icon name="icon-close" />,
      }
    
      const TOASTER_STYLE = {
        '--normal-bg': 'var(--popover)',
        '--normal-text': 'var(--popover-foreground)',
        '--normal-border': 'var(--border)',
        '--border-radius': 'var(--radius)',
      }
    
      return (
        <div class="space-y-4">
          <BaseToaster
            position="top-right"
            preventDuplicate
            id="pd"
            icons={ICONS}
            style={TOASTER_STYLE}
          />
          <Button onClick={() => toast.success('Changes saved!', { toasterId: 'pd' })}>
            Trigger Success Toast
          </Button>
        </div>
      )
    }

    Examples#

    Basic toasts#

    import 'solid-toaster/style.css'
    
    import { Button } from 'moraine'
    import { toast } from 'solid-toaster'
    
    export function BasicToasts() {
      const wait = (ms: number) =>
        new Promise<void>((resolve) => {
          setTimeout(resolve, ms)
        })
    
      const runLoadingToast = async () => {
        const id = toast.loading('Uploading files...')
        await wait(1200)
        toast.success('Upload complete', { id })
      }
    
      return (
        <div class="flex flex-wrap gap-3 items-center">
          <Button onClick={() => toast('Default message', { onAutoClose: console.log })}>
            Default
          </Button>
          <Button variant="secondary" onClick={() => toast.success('Saved successfully')}>
            Success
          </Button>
          <Button variant="outline" onClick={() => toast.warning('Careful with this action')}>
            Warning
          </Button>
          <Button variant="destructive" onClick={() => toast.error('Something went wrong')}>
            Error
          </Button>
          <Button variant="ghost" onClick={runLoadingToast}>
            Loading -&gt; Success
          </Button>
        </div>
      )
    }

    Promise and scoped instances#

    import { Button } from 'moraine'
    import { createSignal } from 'solid-js'
    import { toast } from 'solid-toaster'
    
    export function PromiseScopedInstances() {
      const [promiseRuns, setPromiseRuns] = createSignal(0)
      const wait = (ms: number) =>
        new Promise<void>((resolve) => {
          setTimeout(resolve, ms)
        })
    
      const runPromiseToast = () => {
        const nextRun = promiseRuns() + 1
        setPromiseRuns(nextRun)
        const request = wait(1400).then(() => ({ run: nextRun }))
    
        toast.promise(request, {
          loading: `Sync #${nextRun} in progress...`,
          success: (result) => `Sync #${result.run} finished`,
          error: (error) => `Sync failed: ${String(error)}`,
          duration: 1e6,
        })
      }
    
      return (
        <div class="flex flex-wrap gap-3 items-center">
          <Button onClick={runPromiseToast}>Run promise toast ({promiseRuns()})</Button>
          <Button
            variant="outline"
            onClick={() => toast.info('To bottom-left custom', { toasterId: 'custom' })}
          >
            Send to custom toaster
          </Button>
          <Button variant="ghost" onClick={() => toast.dismiss()}>
            Dismiss all
          </Button>
        </div>
      )
    }