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-element
import { 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 . |
closeLabel close-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. |
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. |
Name | Description | Default |
---|---|---|
--alert-border-radius |
The border radius of the alert. | 0.25rem |
--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-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' |
show 1 |
Instance | Shows the alert. | - |
hide 1 |
Instance | Hides the alert. | - |
toast 1 |
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 theopen
property, 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
.
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>
Note
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 install
Watch for changes and start a development server by running the following command.
npm start
Lint the code by running the following command.
npm run lint
Run the tests by running any of the following commands.
npm test
npm run test:watch # watch mode
Create a production build by running the following command.
npm run build