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.');
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:button","isStatic":true}]},"skaBlocksVariation":"ska-theme:button"} -->
<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:button","isStatic":true}]},"skaBlocksVariation":"ska-theme:button"} -->
<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 {"skaBlocksDisplay":{"v":{"$":{"@":"inline-flex"}}},"skaBlocksFlexDirection":{"v":{"$":{"@":"row"}}},"skaBlocksGap":{"v":{"$":{"@":"2.5"}}},"skaBlocksAlignItems":{"v":{"$":{"@":"center"}}},"skaBlocksPointerEvents":{"v":{"$":{"@":""},"disabled":{"@":"none"}}},"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"}},"skaBlocks":{"cx":"after:[\u0026.loading]:aspect-square inline-flex [\u0026.loading]:overflow-hidden [\u0026.loading]:relative before:[\u0026.loading]:absolute after:[\u0026.loading]:absolute before:[\u0026.loading]:inset-0 after:[\u0026.loading]:top-1/2 after:[\u0026.loading]:left-1/2 before:[\u0026.loading]:z-10 after:[\u0026.loading]:z-20 flex-row gap-2.5 items-center after:[\u0026.loading]:-mt-3 after:[\u0026.loading]:-ml-3 before:[\u0026.loading]:w-full after:[\u0026.loading]:w-6 before:[\u0026.loading]:h-full before:[\u0026.loading]:content-[''] after:[\u0026.loading]:content-[''] before:[\u0026.loading]:bg-site-background/80 after:[\u0026.loading]:rounded-full after:[\u0026.loading]:border-2 after:[\u0026.loading]:border-t-primary after:[\u0026.loading]:border-r-site-border after:[\u0026.loading]:border-b-site-border after:[\u0026.loading]:border-l-site-border after:[\u0026.loading]:animate-spin disabled:pointer-events-none","t":1709756495,"css":".inline-flex{display:inline-flex}.flex-row{flex-direction:row}.items-center{align-items:center}.gap-2{gap:var(\u002d\u002dska-spacing-2)}.gap-2\\.5{gap:var(\u002d\u002dska-spacing-2\\.5)}.disabled\\:pointer-events-none:disabled{pointer-events:none}.\\[\\\u0026\\.loading\\]\\:relative.loading{position:relative}.\\[\\\u0026\\.loading\\]\\:overflow-hidden.loading{overflow:hidden}.before\\:\\[\\\u0026\\.loading\\]\\:absolute.loading::before{content:var(\u002d\u002dtw-content);position:absolute}.before\\:\\[\\\u0026\\.loading\\]\\:inset-0.loading::before{content:var(\u002d\u002dtw-content);inset:var(\u002d\u002dska-spacing-0)}.before\\:\\[\\\u0026\\.loading\\]\\:z-10.loading::before{content:var(\u002d\u002dtw-content);z-index:10}.before\\:\\[\\\u0026\\.loading\\]\\:h-full.loading::before{content:var(\u002d\u002dtw-content);height:100%}.before\\:\\[\\\u0026\\.loading\\]\\:w-full.loading::before{content:var(\u002d\u002dtw-content);width:100%}.before\\:\\[\\\u0026\\.loading\\]\\:bg-site-background\\/80.loading::before{content:var(\u002d\u002dtw-content);background-color:rgba(var(\u002d\u002dska-rgb-site-background),0.8)}.before\\:\\[\\\u0026\\.loading\\]\\:content-\\[\\'\\'\\].loading::before{\u002d\u002dtw-content:'';content:var(\u002d\u002dtw-content)}.after\\:\\[\\\u0026\\.loading\\]\\:absolute.loading::after{content:var(\u002d\u002dtw-content);position:absolute}.after\\:\\[\\\u0026\\.loading\\]\\:left-1\\/2.loading::after{content:var(\u002d\u002dtw-content);left:50%}.after\\:\\[\\\u0026\\.loading\\]\\:top-1\\/2.loading::after{content:var(\u002d\u002dtw-content);top:50%}.after\\:\\[\\\u0026\\.loading\\]\\:z-20.loading::after{content:var(\u002d\u002dtw-content);z-index:20}.after\\:\\[\\\u0026\\.loading\\]\\:-ml-3.loading::after{content:var(\u002d\u002dtw-content);margin-left:calc(var(\u002d\u002dska-spacing-3) * -1)}.after\\:\\[\\\u0026\\.loading\\]\\:-mt-3.loading::after{content:var(\u002d\u002dtw-content);margin-top:calc(var(\u002d\u002dska-spacing-3) * -1)}.after\\:\\[\\\u0026\\.loading\\]\\:aspect-square.loading::after{content:var(\u002d\u002dtw-content);aspect-ratio:1/1}.after\\:\\[\\\u0026\\.loading\\]\\:w-6.loading::after{content:var(\u002d\u002dtw-content);width:var(\u002d\u002dska-spacing-6)}@keyframes spin{to{content:var(\u002d\u002dtw-content);transform:rotate(360deg)}}.after\\:\\[\\\u0026\\.loading\\]\\:animate-spin.loading::after{content:var(\u002d\u002dtw-content);animation:spin 1s linear infinite}.after\\:\\[\\\u0026\\.loading\\]\\:rounded-full.loading::after{content:var(\u002d\u002dtw-content);border-radius:var(\u002d\u002dska-border-radius-full)}.after\\:\\[\\\u0026\\.loading\\]\\:border-2.loading::after{content:var(\u002d\u002dtw-content);border-width:2px}.after\\:\\[\\\u0026\\.loading\\]\\:border-b-site-border.loading::after{content:var(\u002d\u002dtw-content);\u002d\u002dtw-border-opacity:1;border-bottom-color:rgba(var(\u002d\u002dska-rgb-site-border),1)}.after\\:\\[\\\u0026\\.loading\\]\\:border-l-site-border.loading::after{content:var(\u002d\u002dtw-content);\u002d\u002dtw-border-opacity:1;border-left-color:rgba(var(\u002d\u002dska-rgb-site-border),1)}.after\\:\\[\\\u0026\\.loading\\]\\:border-r-site-border.loading::after{content:var(\u002d\u002dtw-content);\u002d\u002dtw-border-opacity:1;border-right-color:rgba(var(\u002d\u002dska-rgb-site-border),1)}.after\\:\\[\\\u0026\\.loading\\]\\:border-t-primary.loading::after{content:var(\u002d\u002dtw-content);\u002d\u002dtw-border-opacity:1;border-top-color:rgba(var(\u002d\u002dska-rgb-primary),1)}.after\\:\\[\\\u0026\\.loading\\]\\:content-\\[\\'\\'\\].loading::after{\u002d\u002dtw-content:'';content:var(\u002d\u002dtw-content)}","p":[{"id":"ska-theme:button","isStatic":true}]},"skaBlocksVariation":"ska-theme:iconButton","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"}}}}}} -->
<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="after:[&.loading]:aspect-square inline-flex [&.loading]:overflow-hidden [&.loading]:relative before:[&.loading]:absolute after:[&.loading]:absolute before:[&.loading]:inset-0 after:[&.loading]:top-1/2 after:[&.loading]:left-1/2 before:[&.loading]:z-10 after:[&.loading]:z-20 flex-row gap-2.5 items-center after:[&.loading]:-mt-3 after:[&.loading]:-ml-3 before:[&.loading]:w-full after:[&.loading]:w-6 before:[&.loading]:h-full before:[&.loading]:content-[''] after:[&.loading]:content-[''] before:[&.loading]:bg-site-background/80 after:[&.loading]:rounded-full after:[&.loading]:border-2 after:[&.loading]:border-t-primary after:[&.loading]:border-r-site-border after:[&.loading]:border-b-site-border after:[&.loading]:border-l-site-border after:[&.loading]: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","t":1709756495,"css":".h-auto{height:auto}.w-4{width:var(\u002d\u002dska-spacing-4)}.text-current{color:currentColor}"},"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>