A custom HTML element for displaying dismissible alerts and toast notifications
Note
The element is heavily inspired by the sl-alert element from Shoelace, especially the API for creating toast notifications.
However, it is not a direct copy and is build from scratch without the use of any external libraries.
npm install --save @georapbox/alert-elementimport { AlertElement } from './node_modules/@georapbox/alert-element/dist/alert-element.js';
// Manually define the element.
AlertElement.defineCustomElement();Alternatively, you can import the automatically defined custom element.
import './node_modules/@georapbox/alert-element/dist/alert-element-defined.js';<alert-element variant="success" open closable>
<strong>Your profile has been updated</strong><br>
All changes have been saved and will take effect immediately.
</alert-element>By default, the component comes with some basic default styling. However, you can customise the styles of the various elements of the component using either CSS Parts or CSS Custom Properties.
| Name | Reflects | Type | Required | Default | Description |
|---|---|---|---|---|---|
closable |
✓ | Boolean | - | false |
Indicates whether the alert can be closed by the user by providing a close button. |
open |
✓ | Boolean | - | false |
Indicates whether the alert is open or not. |
duration |
✓ | Number | - | Infinity |
The duration in milliseconds for which the alert will be displayed before automatically closing. If the user interacts with the alert before it closes, the duration is reset. Defaults to Infinity, which means the alert will not close on its own. |
variant |
✓ | String | - | "" |
The alert's theme variant. Can be one of info, success, neutral, warning, or danger. |
closeLabelclose-label |
✓ | String | - | "Close" |
The label for the default close button. It is used as the aria-label attribute of the close button. If user provides text content for the close button using the close slot, this property is ignored and the aria-label attribute is removed. |
announce |
✓ | String | - | "alert" |
Defines how the alert should be announced to screen readers. Can be one of alert, status, or none. |
countdown |
✓ | Boolean | - | false |
Indicates whether to show a countdown displaying the remaining time before the alert automatically closes. |
noAnimationsno-animations |
✓ | Boolean | - | false |
Disables all animations (default or custom) for showing and hiding the alert. |
customAnimations |
- | Object | - | undefined |
Custom animation keyframes and options for show/hide. The object should contain two properties: show and hide, each containing an object with keyframes and options properties. See Animations for more details. Set to null to disable animations altogether. |
| Name | Description |
|---|---|
| default/unnamed | The default slot for the alert message. |
icon |
Slot to display an icon before the alert message. |
close |
Slot to display custom content for the close button. |
| Name | Description |
|---|---|
base |
The component's base wrapper. |
icon |
The icon element of the alert. |
message |
The message element of the alert. |
close |
The close button element of the alert. |
countdown |
The countdown bar element of the alert. |
countdown-elapsed |
The elapsed portion of the countdown bar. |
| Name | Description | Default |
|---|---|---|
--alert-border-radius |
The border radius of the alert. | 0.25rem |
--alert-top-border-width |
The width of the top border of the alert. | 0.1875rem |
--alert-countdown-height |
The height of the countdown bar. | 0.1875rem |
--alert-fg-color |
The foreground color of the alert. | Light: #3f3f46 - Dark: #b6b6be |
--alert-bg-color |
The background color of the alert. | Light: #ffffff - Dark: #252528 |
--alert-border-color |
The border color of the alert. | Light: #e4e4e7 - Dark: #36363a |
--alert-base-variant-color |
The base color variant for alerts. | var(--alert-fg-color) |
--alert-info-variant-color |
The color variant for info alerts. | Light: #0584c7 - Dark: #27bbfc |
--alert-success-variant-color |
The color variant for success alerts. | Light: #16a34a - Dark: #3ae075 |
--alert-neutral-variant-color |
The color variant for neutral alerts. | Light: #52525b - Dark: #8e8e9a |
--alert-warning-variant-color |
The color variant for warning alerts. | Light: #d87708 - Dark: #ffbd11 |
--alert-danger-variant-color |
The color variant for danger alerts. | Light: #dc2626 - Dark: #fe5c5c |
| Name | Type | Description | Arguments |
|---|---|---|---|
defineCustomElement |
Static | Defines/registers the custom element with the name provided. If no name is provided, the default name is used. The method checks if the element is already defined, hence will skip trying to redefine it. | elementName='alert-element' |
show1 |
Instance | Shows the alert. Returns a promise that resolves when the alert is fully shown and all animations are complete. | - |
hide1 |
Instance | Hides the alert. Returns a promise that resolves when the alert is fully hidden and all animations are complete. | - |
toast1 |
Instance | Displays the alert as a toast notification. See Toast notifications for more details. | { forceRestart: false } |
1 Instance methods are only available after the component has been defined. To ensure the component is defined, you can use whenDefined method of the CustomElementRegistry interface, eg customElements.whenDefined('alert-element').then(() => { /* call methods here */ });
| Name | Description | Event Detail |
|---|---|---|
alert-show |
Emitted when the alert is shown. | null |
alert-after-show |
Emitted after the alert is shown and all animations are complete. | null |
alert-hide |
Emitted when the alert is hidden. | { reason: 'user' | 'timeout' | 'api' } |
alert-after-hide |
Emitted after the alert is hidden and all animations are complete. | { reason: 'user' | 'timeout' | 'api' } |
The events alert-hide and alert-after-hide have a reason property in the event detail that indicates how the alert was hidden. The possible values are:
user- The alert was closed by the user using the close button.timeout- The alert was closed automatically after the duration (if set) has elapsed.api- The alert was closed programmatically by using either thehide()method, or theopenproperty, or the Invoker Commands API.
To display an alert as a toast notification, create an instance of the alert element and call the toast() method.
This will move the alert out of its initial position in the DOM into the toast stack and display it as a toast notification.
When there are more than one toast notifications, they will stack vertically in the toast stack.
The toast stack is a container element that is created and managed internally by the alert element and it will be added in the DOM when there is at least one toast notification to display. If there are no toast notifications to display, the toast stack will be removed from the DOM.
By default, the toast stack is a fixed positioned element that is displayed at the top-right corner of the viewport,
but you can override its position by targeting .alert-toast-stack class in your stylesheet.
Note
The toast stack has a shadow DOM root, so you need to use the ::part pseudo-element to style it.
The reason for this is to encapsulate the styles of the toast stack and prevent them from leaking into the rest of your application.
Below are some examples of how to position the toast stack in the viewport.
/* Position toast stack at the top-center of the viewport */
.alert-toast-stack::part(base) {
right: 50%;
transform: translateX(50%);
}
/* Position toast stack at the top-left of the viewport */
.alert-toast-stack::part(base) {
right: auto;
left: 0;
}
/* Position toast stack at the bottom-left of the viewport */
.alert-toast-stack::part(base) {
right: auto;
left: 0;
top: auto;
bottom: 0;
}
/* Position toast stack at the bottom-center of the viewport */
.alert-toast-stack::part(base) {
right: 50%;
transform: translateX(50%);
top: auto;
bottom: 0;
}
/* Position toast stack at the bottom-right of the viewport */
.alert-toast-stack::part(base) {
top: auto;
bottom: 0;
}The default styles of the toast stack are as follows:
.alert-toast-stack::part(base) {
position: fixed;
top: 0;
right: 0;
z-index: 1000;
width: 30rem;
max-width: 100%;
max-height: 100%;
overflow: auto;
scroll-behavior: smooth;
scrollbar-width: none;
}For convenience, you can create a utility function that creates a toast notification imperatively,
instead of creating the alert elements in your markup. To do this, you can generate the alert element
using JavaScript and call the toast() method on it.
<button type="button">Create toast</button>const button = document.querySelector('button');
button.addEventListener('click', () => {
toastify('This is a toast notification', {
variant: 'success',
duration: 3000,
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="1.25em" height="1.25em" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/>
<path d="m10.97 4.97-.02.022-3.473 4.425-2.093-2.094a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-1.071-1.05"/>
</svg>`
});
});
function escapeHtml(html) {
const div = document.createElement('div');
div.textContent = html;
return div.innerHTML;
}
function toastify(message, options = {}) {
const defaults = {
duration: 3000,
variant: 'neutral',
icon: ''
};
options = { ...defaults, ...options };
const icon = options.icon ? `<span slot="icon">${options.icon}</span>` : '';
const alert = Object.assign(document.createElement('alert-element'), {
closable: true, // Always provide a way to close the toast notification
duration: options.duration,
variant: options.variant,
innerHTML: `${icon}${escapeHtml(message)}` // Escape message to prevent XSS ig you don't control the content
});
return alert.toast();
}The element uses the Web Animations API to animate the alert when it is shown or hidden.
It comes with a default animation, but you can override it by providing your own animation options, either globally using the static AlertElement.customAnimations property or per instance using the customAnimations property.
For example, you can override the default animation options for the show and hide animations as follows:
const customAnimations = {
show: {
keyframes: [
{ opacity: 0, transform: 'rotateX(90deg) scale(0.8)' },
{ opacity: 1, transform: 'rotateX(-10deg) scale(1.05)' },
{ opacity: 1, transform: 'rotateX(5deg) scale(0.97)' },
{ opacity: 1, transform: 'rotateX(0deg) scale(1)' }
],
options: {
duration: 600,
easing: 'cubic-bezier(0.22, 1, 0.36, 1)'
}
},
hide: {
keyframes: [
{ opacity: 1, transform: 'scale(1)' },
{ opacity: 0, transform: 'scale(0.8)' }
],
options: {
duration: 400,
easing: 'ease-in'
}
}
};
// Set the custom animations globally
AlertElement.customAnimations = customAnimations;
// Or set the custom animations for a specific instance
const alert = document.querySelector('alert-element');
alert.customAnimations = customAnimations;Note
Animations respect the users' prefers-reduced-motion setting.
If the user has enabled the setting on their system, the animations will be disabled and the element will be shown/hidden instantly.
To disable animations for all users no matter their settings, you can set the customAnimations property to null.
As of version 1.2.0, you can also disable animations using the no-animations attribute or noAnimations property.
This provides a more declarative way to disable animations without having to set custom animations to null,
although setting customAnimations to null will still work as before.
The alert element reacts gracefully to user interactions, making sure it stays visible and accessible while the user is engaged with it.
When an alert has a finite duration, it automatically closes after the specified time has elapsed. However, if the user interacts with the alert — for example, by hovering over it with the mouse or focusing it using the keyboard — the auto-dismiss timer temporarily pauses, ensuring that the alert does not close while the user is reading or interacting with it.
Once the interaction ends (e.g., when the mouse leaves or focus moves away from the alert), the timer resumes from where it left off, continuing the countdown until the remaining time expires.
This behavior applies to all focusable elements inside the alert (such as the close button or any custom slotted controls), ensuring that keyboard and assistive-technology users receive the same non-interruptive experience as pointer users.
Note
The interaction handling is implemented using both mouseenter/mouseleave and focusin/focusout events.
In modern browsers, focus events from within the alert's shadow DOM are automatically retargeted to the host element,
so the component can pause and resume correctly even when the focus is inside shadow DOM content.
The element supports the Invoker Commands API, enabling a more declarative way to control alerts. Use the following commands:
--alert-show- shows the alert element--alert-hide- hides the alert element
To trigger these commands, add the commandfor and command attributes to a button, targeting the alert element by its id attribute.
<button type="button" commandfor="myAlert" command="--alert-show">Show Alert</button>
<button type="button" commandfor="myAlert" command="--alert-hide">Hide Alert</button>
<alert-element variant="info" id="myAlert">
<strong>Alert with invoker commands</strong><br>
This alert uses invoker commands to toggle its visibility.
</alert-element>Important
This is an experimental feature based on the Invoker Commands API. The API is still in the early stages and might change in the future. Check browser compatibility before using it in production.
For API updates and breaking changes, check the CHANGELOG.
The project requires Node.js and npm to be installed on your environment. Preferrably, use nvm Node Version Manager and use the version of Node.js specified in the .nvmrc file by running nvm use.
Install the project dependencies by running the following command.
npm installWatch for changes and start a development server by running the following command.
npm startLint the code by running the following command.
npm run lintRun the tests by running any of the following commands.
npm test
npm run test:watch # watch modeCreate a production build by running the following command.
npm run build