A SolidJS toast library
- Solidjs context api
- Uses the context api to create its own scope
- Exposes five methods: notify, dismiss, update, remove and custom
- Toast features
- Easily customizable
- Pass string or JSX as toast body
- Control the position
- Update the toast and have the other toasts react to the height changes
- Custom entrance and exit animations
- On enter, update and exit callbacks
- Automatic toast screen overflow prevention
- Pause on hover / pause on tab change
- Customizable Progress Bar
- Customize the look and the position
- Customize the progress animation
- Play, pause and reset controls
Installation:
npm i solid-moon-toast
# or
yarn add solid-moon-toast
# or
pnpm add solid-moon-toast
Usage:
// App.tsx
import { ToastProvider } from 'solid-moon-toast'
import "solid-moon-toast/dist/index.css";
import ToastsPage from './ToastsPage'
const App = () => {
return (
<ToastProvider>
<ToastsPage />
</ToastProvider>
)
}
export default App
// ToastsPage.tsx
import { useToast } from "solid-moon-toast";
const ToastsPage = () => {
const { notify } = useToast();
return (
<div>
<button onClick={() => notify("🎉 Operation Successful!")}>
</div>
)
}
Toast options are divided into global and per toast options.
These settings can be passed as props to the ToastProvider component.
gutter={16} // distance between the toasts (global only)
maxToasts={10} // set to 0 if you dont want to limit the number of toasts (global only)
offsetX={16} // distance from the screen edge on the X axis (global only)
offsetY={16} // distance from the screen edge on the Y axis (global only)
positionX="right" // position on the X axis, accepts: left | center | right (global only)
positionY="top" // position on the Y axis, accepts: top | bottom (global only)
toasterStyle={{
"background-color": "blue", // custom style for the toaster (global only)
}}
pauseOnTabSwitch={true} // pause the toast timer when switching browser tabs (global only)
These settings can be applied either globally on the ToastProvider component (all except the toast type), or as individual toast options. Any setting passed to the individual toast will override the corresponding global setting.
type="success" // type of the toast, accepts: 'success' | 'error' | 'loading'
class={{ className: "my-toast-class", replaceDefault: true }} // custom toast class, choose to replace default or not
style={{ "background-color": "blue" }} // custom style for the toast
dismissButton={{
className: "my-dismiss-class", // custom class for dismiss button
show: true, // show dismiss button
style: { color: "red" }, // custom style for dismiss button
type: "floating", // floating or inline
}}
dissmisOnClick={false} // dismiss toast when clicking on the body
duration="infinite" // duration in ms or infinite
enterCallback={() => null} // pass a function that will be called on toast enter
updateCallback={() => null} // pass a function that will be called on toast update
exitCallback={() => null} // pass a function that will be called on toast exit
onEnter="my-entrance-animation" // class that will be applied to the toast container on enter
onIdle="my-idle-animation" // class that will be applied to the toast container when idle
onExit="my-exit-animation" // class that will be applied to the toast container on exit
enterDuration={500} // entrance duration; should be the same as the duration of the entrance animation
exitDuration={500} // entrance duration; should be the same as the duration of the exit animation
pauseOnHover={true} // pause the toast timer when hovering over it
progressBar={{
showDefault: true, // show the default progress bar
className: "my-progress-bar-class", // custom class for progress bar
style: { "background-color": "red" }, // custom style for progress bar
animate: { // pass the arguments to the el.animate() method which will be called on the progress bar
keyframes: [{ width: "0%" }, { width: "100%" }], // pass the keyframes, the options or both
options: { // pass the keyframes, the options or both
duration: 5000,
fill: "forwards",
easing: "linear",
},
},
}}
wrapperClass={{
className: "my-wrapper-class",
replaceDefault: false,
}} // custom wrapper class, choose to replace default or not
icon={<svg></svg>} // custom icon for the toast, accepts a JSX element
iconWrapperClass={{
className: "my-icon-wrapper-class", // custom icon wrapper class
replaceDefault: false, // replace default icon wrapper
}}
iconWrapperStyle={{
"background-color": "red", // custom style for the icon wrapper
}}
aria={{ "aria-live": "polite", role: "status" }} // accessibility props
unstyled={false} // remove all default styles of the toast, doesn't affect the toast wrapper
The useToast function returns an object with five methods.
import {useToast} from "solid-moon-toast";
...
const {notify, update, dismiss, remove, custom} = useToast();
Creates a new toast. Accepts a string or jsx as the first argument, and options (common options) as the second argument.
Returns an id, a ref to the toast element, and a timer:const { id, ref, timer } = notify('My first toast!', { duration: 5000 })
const toastId = 'toast-1'
const { id, ref, timer } = notify('My first toast!', { id: toastId })
const { timer } = notify()
timer.pause()
timer.play()
timer.reset()
Updates an existing toast. Accepts a string or jsx as the first argument, and options (common options) as the second argument. Passing an id as an option is required.
Returns an id, a ref to the toast element, and a timer:const {id, ref, timer} = update("Updating toast...", id: "toast-1")
const { notify, update } = useToast()
const getData = async () => {
const { id } = notify('Fetching data...', { type: 'loading' })
const response = await fetch('http://example.com/api')
const data = await response.json()
if (!data) return update('Error fetching data.', { id, type: 'error' })
update('Successfully fetched data!', { id, type: 'success' })
return data
}
Dismisses a toast. Call with an id to dismiss a specific toast, or with no arguments to dismiss all toasts.
dismiss('toast-1')
dismiss() // dismisses all toasts
Removes a toast immediately, without triggering the exit animation. Call with an id to remove a specific toast, or with no arguments to remove all toasts.
remove('toast-1')
remove() // removes all toasts
Provide your own toast body. Provides id, timer, and duration as args. Returns id, ref and timer. Accepts limited options.
const {id, ref, timer: timerControls} = custom({id, duration, timer} => (
<div>
<button onClick={()=> timer.pause()}>Pause toast</button>
</div>
), {
onEnter: "slide-in-top"
});
// options:
| 'duration'
| 'dismissButton'
| 'dissmisOnClick'
| 'enterDuration'
| 'exitDuration'
| 'id'
| 'onEnter'
| 'onExit'
| 'onIdle'
| 'pauseOnHover'
| 'progressBar'
| 'wrapperClass'
You can use a custom progress bar by providing an element with a data-role="progress" property. After that, set the toast options to not show the default progress bar. The progress bar can be controlled with the timer prop.
Example:const {timer} = notify(
<>
<div class="px-1">
<div class="relative flex gap-3">
<p class="font-medium">
This is a custom toast.
</p>
<button onClick={() => timer.pause()}>Pause timer</button>
</div>
</div>
<div
data-role="progress"
class="pointer-events-none absolute left-0 top-0 h-full w-full bg-blue-600/20"
/>
</>,
{
duration: 10000,
progressBar: { showDefault: false }, // hide the default progress bar
}
);
To add a custom animation to the progress bar, pass keyframes and / or options to progressBar: {animate: {keyframes, options}}
, which will be called by the Element: animate() method on the progress bar dom element.
notify(
<>
<div>
<p>
You can customize the progress bar.
</p>
</div>
<div
data-role="progress"
class="pointer-events-none absolute left-0 top-0 h-full w-full bg-blue-600/20"
/>
</>,
{
progressBar: {
style: {
background: 'radial-gradient(circle, rgba(63,94,251,1) 0%, rgba(252,70,107,1) 100%)',
},
animate: {
keyframes: [{ transform: 'scaleX(0)' }, { transform: 'scaleX(1)' }],
},
},
});
This error occurs when passing a jsx component as the first argument to the create toast functions. You can fix it by wrapping the component with createRoot().
notify(<MyComponent message="This is a test message" />); // will log "computations created outside a `createRoot` or `render` will never be disposed"
notify(createRoot(()=> <MyComponent message="This is a test message" />)) // no error