Display notifications that appear on the front end to provide feedback or information to the user.

ska-theme includes a template part for rendering Toasts, which can be enabled from ska-theme -> General -> Toasts. The template part is set up to use the skaToast Alpine.js module.

Module specificationTypeScript
// Available configuration when using `x-data="skaToast"` on an element.
interface ToastConfig {
	/** Maximum amount of Toasts visible at once. */
	max?: number
}

// Toast structure.
interface Toast {
	id?: string | number
	content: string
	type?: 'default' | 'info' | 'success' | 'warning' | 'error' | string
}

// Properties available when using `x-data="skaToast"` on an element.
interface ToastModule {
	/** Add and show a toast. */
	add: (toast: Toast | Toast['content']) => void
	/** Show a toast. */
	show: (id: Toast['id']) => void
	/** Hide a toast. */
	hide: (id: Toast['id']) => void
	/** Remove all toasts. */
	clear: () => void
}

Adding a toast

Ensure that rendering of the template part that handles showing toasts is enabled from ska-theme -> General -> Toast.

JavaScript

The global window.ska_theme object includes a function for adding a toast. The function can accept a string with toast content or an object with the content property and additional details about the toast, such as type or id.

Adding a toast with JavaScriptJavaScript
// Using the global function
window.ska_theme.toast('Toast content.')

// With additional details
window.ska_theme.toast({
	content: 'Toast content.',
	type: 'info',
})

// Dispatching the event manually
window.dispatchEvent(new CustomEvent('ska-toast', {
	detail: {
		content: 'Toast content.',
	},
}))

// Toast with details
window.ska_theme.toast({
	id: 'toast-1',
	content: 'Toast content.',
	type: 'info',
})
// Hide toast by ID
window.ska_theme.hideToast('toast-1')
// Hide all toasts
window.ska_theme.clearToasts()

Alpine.js

In the block editor it’s easiest to add toasts by adding HTML attributes to blocks. A toast can be triggered by using the ska_theme.toast function from earlier or by using Alpine’s $dispatch magic.

Adding a toast with Alpine.jsHTML
/** Using the global function */
<button x-data x-on:click="ska_theme.toast('Toast content.')">Show toast</button>

// Add toast
ska_theme.toast({id: 'toast-1',	content: 'Toast content.', type: 'info'})

// Hide toast
ska_theme.hideToast('toast-1')

// Hide all toasts
ska_theme.clearToasts()

/** Using Alpine.js $dispatch */
<button x-data x-on:click="$dispatch('ska-toast', 'Toast content.')">Show toast</button>

// Add toast
$dispatch('ska-toast', {content: 'Toast content.', type: 'success', id: 'toast-id'})

// Hide toast
$dispatch('ska-hide-toast', 'toast-id')

// Hide all toasts
$dispatch('ska-clear-toasts')

PHP

A toast can also be added with PHP to leave users some feedback when something was done server-side.

Adding a toast with PHPPHP
ska_theme()->get('toast')->add('Toast content.');

ska_theme()->get('toast')->add([
	'content' => 'Invalid nonce.', 
	'type' => 'error',
]);
PHP examplePHP
add_action('wp_body_open', function() {

	if(!isset($_POST['email'])) {
		return;
	}

	if(subscribe_to_newsletter(sanitize_email(wp_unslash($_POST['email'])))) {
		ska_theme()->get('toast')->add([
			'content' => esc_html__('You have subscribed to the newsletter!', 'your-textdomain'),
			'type' => 'success',
		]);
	} else {
		ska_theme()->get('toast')->add([
			'content' => sprintf(
				esc_html__('Something went %1$shorribly%2$s wrong.', 'your-textdomain'),
				'<strong>',
				'</strong>'
			),
			'type' => 'error',
		]);
	}
});

Examples

Default toast

<!-- wp:ska/text {"skaBlocksAs":{"element":"custom","customElement":"button"},"skaBlocksAttributes":{"record":{"x-data":"","x-on:click":"$dispatch('ska-toast', 'Toast content.')"}},"skaBlocks":{"p":[{"id":"ska-theme\u002d\u002dbutton","isStatic":true}],"t":1738813197},"skaBlocksVariation":"ska-theme\u002d\u002dbutton"} -->
<button x-data="" x-on:click="$dispatch('ska-toast', 'Toast content.')" class="wp-block-ska-text ska-text">Default toast</button>
<!-- /wp:ska/text -->

Detailed toast

<!-- wp:ska/text {"skaBlocksAs":{"element":"custom","customElement":"button"},"skaBlocksAttributes":{"record":{"x-data":"","x-on:click":"$dispatch('ska-toast', {id: 'toast', content: 'Toast content.', type: 'info'})"}},"skaBlocks":{"p":[{"id":"ska-theme\u002d\u002dbutton","isStatic":true}],"t":1738813197},"skaBlocksVariation":"ska-theme\u002d\u002dbutton"} -->
<button x-data="" x-on:click="$dispatch('ska-toast', {id: 'toast', content: 'Toast content.', type: 'info'})" class="wp-block-ska-text ska-text">Detailed toast</button>
<!-- /wp:ska/text -->

Remote request

A button that attempts to fetch data from a REST endpoint and displays a success toast with the result or an error toast on failure (includes a setTimeout for demo purposes only).

<!-- wp:ska/element {"skaBlocks":{"cx":"[\u0026.loading]:after:aspect-square inline-flex [\u0026.loading]:overflow-hidden [\u0026.loading]:relative [\u0026.loading]:before:absolute [\u0026.loading]:after:absolute [\u0026.loading]:before:inset-0 [\u0026.loading]:after:top-1/2 [\u0026.loading]:after:left-1/2 [\u0026.loading]:before:z-10 [\u0026.loading]:after:z-20 flex-row gap-2.5 items-center [\u0026.loading]:after:-mt-3 [\u0026.loading]:after:-ml-3 [\u0026.loading]:before:w-full [\u0026.loading]:after:w-6 [\u0026.loading]:before:h-full [\u0026.loading]:before:content-[''] [\u0026.loading]:after:content-[''] [\u0026.loading]:before:bg-site-background/80 [\u0026.loading]:after:rounded-full [\u0026.loading]:after:border-2 [\u0026.loading]:after:border-t-primary [\u0026.loading]:after:border-r-site-border [\u0026.loading]:after:border-b-site-border [\u0026.loading]:after:border-l-site-border [\u0026.loading]:after:animate-spin disabled:pointer-events-none","css":".\\[\\\u0026\\.loading\\]\\:after\\:aspect-square.loading:after{content:var(\u002d\u002dtw-content);aspect-ratio:1}.inline-flex{display:inline-flex}.\\[\\\u0026\\.loading\\]\\:overflow-hidden.loading{overflow:hidden}.\\[\\\u0026\\.loading\\]\\:relative.loading{position:relative}.\\[\\\u0026\\.loading\\]\\:before\\:absolute.loading:before{content:var(\u002d\u002dtw-content);position:absolute}.\\[\\\u0026\\.loading\\]\\:after\\:absolute.loading:after{content:var(\u002d\u002dtw-content);position:absolute}.\\[\\\u0026\\.loading\\]\\:before\\:inset-0.loading:before{content:var(\u002d\u002dtw-content);inset:var(\u002d\u002dspacing-0)}.\\[\\\u0026\\.loading\\]\\:after\\:top-1\\/2.loading:after{content:var(\u002d\u002dtw-content);top:50%}.\\[\\\u0026\\.loading\\]\\:after\\:left-1\\/2.loading:after{content:var(\u002d\u002dtw-content);left:50%}.\\[\\\u0026\\.loading\\]\\:before\\:z-10.loading:before{content:var(\u002d\u002dtw-content);z-index:10}.\\[\\\u0026\\.loading\\]\\:after\\:z-20.loading:after{content:var(\u002d\u002dtw-content);z-index:20}.flex-row{flex-direction:row}.gap-2\\.5{gap:var(\u002d\u002dspacing-2_5)}.items-center{align-items:center}.\\[\\\u0026\\.loading\\]\\:after\\:-mt-3.loading:after{content:var(\u002d\u002dtw-content);margin-top:calc(var(\u002d\u002dspacing-3)*-1)}.\\[\\\u0026\\.loading\\]\\:after\\:-ml-3.loading:after{content:var(\u002d\u002dtw-content);margin-left:calc(var(\u002d\u002dspacing-3)*-1)}.\\[\\\u0026\\.loading\\]\\:before\\:w-full.loading:before{content:var(\u002d\u002dtw-content);width:100%}.\\[\\\u0026\\.loading\\]\\:after\\:w-6.loading:after{content:var(\u002d\u002dtw-content);width:var(\u002d\u002dspacing-6)}.\\[\\\u0026\\.loading\\]\\:before\\:h-full.loading:before{content:var(\u002d\u002dtw-content);height:100%}.\\[\\\u0026\\.loading\\]\\:before\\:content-\\[\\'\\'\\].loading:before{content:var(\u002d\u002dtw-content);\u002d\u002dtw-content:\u0022\u0022;content:var(\u002d\u002dtw-content)}.\\[\\\u0026\\.loading\\]\\:after\\:content-\\[\\'\\'\\].loading:after{content:var(\u002d\u002dtw-content);\u002d\u002dtw-content:\u0022\u0022;content:var(\u002d\u002dtw-content)}.\\[\\\u0026\\.loading\\]\\:before\\:bg-site-background\\/80.loading:before{content:var(\u002d\u002dtw-content);background-color:color-mix(in oklab,var(\u002d\u002dcolor-site-background)80%,transparent)}.\\[\\\u0026\\.loading\\]\\:after\\:rounded-full.loading:after{content:var(\u002d\u002dtw-content);border-radius:3.40282e38px}.\\[\\\u0026\\.loading\\]\\:after\\:border-2.loading:after{content:var(\u002d\u002dtw-content);border-style:var(\u002d\u002dtw-border-style);border-width:2px}.\\[\\\u0026\\.loading\\]\\:after\\:border-t-primary.loading:after{content:var(\u002d\u002dtw-content);border-top-color:var(\u002d\u002dcolor-primary)}.\\[\\\u0026\\.loading\\]\\:after\\:border-r-site-border.loading:after{content:var(\u002d\u002dtw-content);border-right-color:var(\u002d\u002dcolor-site-border)}.\\[\\\u0026\\.loading\\]\\:after\\:border-b-site-border.loading:after{content:var(\u002d\u002dtw-content);border-bottom-color:var(\u002d\u002dcolor-site-border)}.\\[\\\u0026\\.loading\\]\\:after\\:border-l-site-border.loading:after{content:var(\u002d\u002dtw-content);border-left-color:var(\u002d\u002dcolor-site-border)}.\\[\\\u0026\\.loading\\]\\:after\\:animate-spin.loading:after{content:var(\u002d\u002dtw-content);animation:var(\u002d\u002danimate-spin)}.disabled\\:pointer-events-none:disabled{pointer-events:none}","t":1738813197,"p":[{"id":"ska-theme\u002d\u002dbutton","isStatic":true}]},"skaBlocksAppender":{"type":"hidden"},"skaBlocksAs":{"element":"custom","customElement":"button"},"skaBlocksAttributes":{"record":{"x-data":"{loading: false}","x-on:fetch":"fetch('/docs/wp-json/wp/v2/posts?per_page=1').then(r =\u003e r.json()).then(p =\u003e p[0].title.rendered).then(title =\u003e $dispatch('success', title)).catch(e =\u003e $dispatch('error')).finally(() =\u003e loading = false)","x-on:success":"$dispatch('ska-toast', {content: `The latest post on this site is titled: \u003cstrong\u003e${$event.detail}\u003c/strong\u003e`, type: 'success'})","x-on:error":"$dispatch('ska-toast', {content: `Failed fetching the latest post.`, type: 'error'})","x-on:click":"loading = true; setTimeout(() =\u003e $dispatch('fetch'), 2000)",":class":"{loading}",":disabled":"loading"}},"skaBlocksSelectors":{"[\u0026.loading]":{"skaBlocksSelectors":{"before":{"skaBlocksPosition":{"v":{"$":{"@":"absolute"}}},"skaBlocksContent":{"v":{"$":{"@":"['']"}},"a":["['']"]},"skaBlocksWidth":{"v":{"$":{"@":"full"}}},"skaBlocksHeight":{"v":{"$":{"@":"full"}}},"skaBlocksTopRightBottomLeft":{"v":{"$":{"@":"","i":"0"}},"t":"inset"},"skaBlocksBackgroundColor":{"v":{"$":{"@":"site-background/80"}}},"skaBlocksZIndex":{"v":{"$":{"@":"10"}}}},"after":{"skaBlocksPosition":{"v":{"$":{"@":"absolute"}}},"skaBlocksContent":{"v":{"$":{"@":"['']"}},"a":["['']"]},"skaBlocksTopRightBottomLeft":{"v":{"$":{"@":"","t":"1/2","l":"1/2"}}},"skaBlocksWidth":{"v":{"$":{"@":"6"}}},"skaBlocksBorderRadius":{"v":{"$":{"@":"full"}}},"skaBlocksBorderColor":{"v":{"$":{"@":"","t":"primary","r":"site-border","b":"site-border","l":"site-border"}},"a":[],"t":"sides"},"skaBlocksBorderWidth":{"v":{"$":{"@":"2"}}},"skaBlocksAspectRatio":{"v":{"$":{"@":"square"}}},"skaBlocksAnimation":{"v":{"$":{"@":"spin"}}},"skaBlocksMargin":{"v":{"$":{"@":"","t":"-3","l":"-3"}},"t":"sides"},"skaBlocksZIndex":{"v":{"$":{"@":"20"}}}}},"skaBlocksOverflow":{"v":{"$":{"@":"hidden"}}},"skaBlocksPosition":{"v":{"$":{"@":"relative"}}}}},"skaBlocksVariation":"ska-theme\u002d\u002dicon-button","skaBlocksDisplay":{"v":{"$":{"@":"inline-flex"}}},"skaBlocksFlexDirection":{"v":{"$":{"@":"row"}}},"skaBlocksGap":{"v":{"$":{"@":"2.5"}}},"skaBlocksAlignItems":{"v":{"$":{"@":"center"}}},"skaBlocksPointerEvents":{"v":{"$":{"@":""},"disabled":{"@":"none"}}}} -->
<button x-data="{loading: false}" x-on:fetch="fetch('/docs/wp-json/wp/v2/posts?per_page=1').then(r => r.json()).then(p => p[0].title.rendered).then(title => $dispatch('success', title)).catch(e => $dispatch('error')).finally(() => loading = false)" x-on:success="$dispatch('ska-toast', {content: `The latest post on this site is titled: <strong>${$event.detail}</strong>`, type: 'success'})" x-on:error="$dispatch('ska-toast', {content: `Failed fetching the latest post.`, type: 'error'})" x-on:click="loading = true; setTimeout(() => $dispatch('fetch'), 2000)" :class="{loading}" :disabled="loading" class="[&.loading]:after:aspect-square inline-flex [&.loading]:overflow-hidden [&.loading]:relative [&.loading]:before:absolute [&.loading]:after:absolute [&.loading]:before:inset-0 [&.loading]:after:top-1/2 [&.loading]:after:left-1/2 [&.loading]:before:z-10 [&.loading]:after:z-20 flex-row gap-2.5 items-center [&.loading]:after:-mt-3 [&.loading]:after:-ml-3 [&.loading]:before:w-full [&.loading]:after:w-6 [&.loading]:before:h-full [&.loading]:before:content-[''] [&.loading]:after:content-[''] [&.loading]:before:bg-site-background/80 [&.loading]:after:rounded-full [&.loading]:after:border-2 [&.loading]:after:border-t-primary [&.loading]:after:border-r-site-border [&.loading]:after:border-b-site-border [&.loading]:after:border-l-site-border [&.loading]:after:animate-spin disabled:pointer-events-none wp-block-ska-element"><!-- wp:ska/text -->
<span class="wp-block-ska-text ska-text">Fetch the latest post title</span>
<!-- /wp:ska/text -->

<!-- wp:ska/image {"mode":"icon","svg":"\u003csvg fill=\u0022currentColor\u0022 xmlns=\u0022http://www.w3.org/2000/svg\u0022 viewBox=\u00220 0 512 512\u0022\u003e\u003c!\u002d\u002d! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc. \u002d\u002d\u003e\n\t\u003cpath d=\u0022M470.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L402.7 256 265.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160zm-352 160l160-160c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L210.7 256 73.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0z\u0022 /\u003e\n\u003c/svg\u003e","collection":"font-awesome-6","icon":"solid/angles-right","wrap":false,"sanitize":false,"skaBlocks":{"cx":"w-4 h-auto text-current","css":".w-4{width:var(\u002d\u002dspacing-4)}.h-auto{height:auto}.text-current{color:currentColor}","t":1738813197},"skaBlocksWidth":{"v":{"$":{"@":"4"}}},"skaBlocksHeight":{"v":{"$":{"@":"auto"}}},"skaBlocksTextColor":{"v":{"$":{"@":"current"}}}} -->
<div role="figure" aria-hidden="true" class="w-4 h-auto text-current wp-block-ska-image"><svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc. -->
	<path d="M470.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L402.7 256 265.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160zm-352 160l160-160c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L210.7 256 73.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0z" />
</svg></div>
<!-- /wp:ska/image --></button>
<!-- /wp:ska/element -->
Alpine.js attributes that the button usesHTML
<button 
	x-data="{loading: false}" 
	x-on:fetch="fetch('/docs/wp-json/wp/v2/posts?per_page=1')
		.then(response => response.json())
		.then(post => post[0].title.rendered)
		.then(title => $dispatch('success', title))
		.catch(e => $dispatch('error'))
		.finally(() => loading = false)" 
	x-on:success="$dispatch('ska-toast', {content: `The latest post on this site is titled: <strong>${$event.detail}</strong>`, type: 'success'})" 
	x-on:error="$dispatch('ska-toast', {content: `Failed fetching the latest post.`, type: 'error'})" 
	x-on:click="loading = true; $dispatch('fetch')" 
	:class="{loading}" 
	:disabled="loading"
>
	...
</button>