The confirmation of an action just taken, with one button to take it back. Only the last one is worth offering, so a new bar replaces the one showing rather than stacking under it.
Usage
vue
<script setup lang="ts">
import { VButton, snackbar } from 'vectis-ui'
function remove() {
snackbar({ message: 'Message deleted.', actionText: 'Undo', action: () => {} })
}
</script>
<template>
<!-- Mount <VSnackbar /> once, at the root of your application. This site does it in its layout,
which is why the example does not: a second one would render the same bar twice. -->
<VButton variant="outline" tone="neutral" @click="remove">Delete</VButton>
</template>
Examples
Tones
tone offers two values and no more: a confirmation says either that something worked or that it did not. Both are painted solid.
vue
<script setup lang="ts">
import { VButton, snackbar } from 'vectis-ui'
</script>
<template>
<!-- Raised into the <VSnackbar /> mounted once at the root of the application. -->
<div class="demo">
<!-- Two tones and deliberately no more: a confirmation says either "done" or "that
did not work". Success, warning and the accent are states, which is what a
notification reports. -->
<VButton
variant="outline"
tone="neutral"
@click="snackbar({ message: 'Conversation archived.', action: () => {} })"
>
Archive
</VButton>
<VButton
variant="outline"
tone="danger"
@click="
snackbar({
tone: 'danger',
message: 'Could not save your changes.',
actionText: 'Retry',
action: () => {},
})
"
>
Failing action
</VButton>
</div>
</template>
<style scoped>
.demo {
display: flex;
flex-wrap: wrap;
gap: var(--vectis-space-3);
}
</style>
Without an action
Leave the action out and the bar carries no button at all. When there is one, running it always takes the bar away.
vue
<script setup lang="ts">
import { VButton, snackbar } from 'vectis-ui'
</script>
<template>
<!-- Raised into the <VSnackbar /> mounted once at the root of the application. -->
<!-- No `action`, no button at all. The bar is then a plain statement of what just
happened, and it takes itself away on its own. -->
<VButton variant="outline" tone="neutral" @click="snackbar({ message: 'Settings saved.' })">
Save
</VButton>
</template>
With an icon
icon is opt-in, and none is deduced from the tone.
vue
<script setup lang="ts">
import { VButton, snackbar } from 'vectis-ui'
import { cloud_upload as cloudUpload } from 'vectis-ui/icons'
</script>
<template>
<!-- Raised into the <VSnackbar /> mounted once at the root of the application. -->
<!-- An icon is opt-in, and none is deduced from the tone: a confirmation is read,
not scanned, so the words carry it and the icon only ever adds to them. -->
<VButton
variant="outline"
tone="neutral"
@click="snackbar({ message: 'File uploaded.', icon: cloudUpload, action: () => {} })"
>
Upload
</VButton>
</template>
Placements
placement puts the bar along the bottom edge, to the start, the centre or the end. Set on the VSnackbar it is the default for every bar; passed when one is raised it is that bar's alone.
vue
<script setup lang="ts">
import { VButton, snackbar, type SnackbarPlacement } from 'vectis-ui'
const placements: SnackbarPlacement[] = ['bottom-left', 'bottom-center', 'bottom-right']
</script>
<template>
<!-- Raised into the <VSnackbar /> mounted once at the root of the application. -->
<div class="demo">
<!-- Along the bottom edge and nowhere else: a confirmation belongs where it is out
of the content's way and close to what the reader was doing. Set on the
VSnackbar it is the default for every bar; passed here it is this bar's own. -->
<VButton
v-for="placement in placements"
:key="placement"
variant="outline"
tone="neutral"
@click="snackbar({ placement, message: `Raised at ${placement}.`, action: () => {} })"
>
{{ placement }}
</VButton>
</div>
</template>
<style scoped>
.demo {
display: flex;
flex-wrap: wrap;
gap: var(--vectis-space-3);
}
</style>
One at a time
There is at most one bar: raising a second replaces the first on the spot, countdown restarted.
vue
<script setup lang="ts">
import { VButton, snackbar } from 'vectis-ui'
</script>
<template>
<!-- Raised into the <VSnackbar /> mounted once at the root of the application. -->
<div class="demo">
<!-- There is at most one bar. Raising a second replaces the first on the spot and
restarts the countdown from the top, which is what separates a confirmation
from a notification: two states can be true at once, but only the last action
a reader took is worth offering to undo. -->
<VButton
variant="outline"
tone="neutral"
@click="snackbar({ message: 'Message deleted.', duration: 0, action: () => {} })"
>
Delete
</VButton>
<VButton
variant="outline"
tone="neutral"
@click="snackbar({ message: 'Conversation archived.', duration: 0, action: () => {} })"
>
Archive
</VButton>
</div>
</template>
<style scoped>
.demo {
display: flex;
flex-wrap: wrap;
gap: var(--vectis-space-3);
}
</style>
How long it stays
duration is how long the bar stays, four seconds by default. The countdown holds while the pointer rests on the bar and while the keyboard is inside it, and is released only when both are gone.
vue
<script setup lang="ts">
import { VButton, snackbar } from 'vectis-ui'
</script>
<template>
<!-- Raised into the <VSnackbar /> mounted once at the root of the application. -->
<div class="demo">
<!-- Four seconds by default, shorter than a notification's: the reader already
knows what the bar says, having just done it. The countdown holds while the
pointer rests on the bar and while the keyboard is inside it, so an action
being reached for is never taken away mid-reach. -->
<VButton
variant="outline"
tone="neutral"
@click="snackbar({ message: 'Gone in 1.5 seconds.', duration: 1500, action: () => {} })"
>
1.5 seconds
</VButton>
<VButton
variant="outline"
tone="neutral"
@click="snackbar({ message: 'Gone in 8 seconds.', duration: 8000, action: () => {} })"
>
8 seconds
</VButton>
</div>
</template>
<style scoped>
.demo {
display: flex;
flex-wrap: wrap;
gap: var(--vectis-space-3);
}
</style>
Keeping it until it is taken away
A duration of 0 disarms the countdown, so the bar stays until something replaces it or takes it away. snackbar hands back an id, and dismissSnackbar takes that bar away.
vue
<script setup lang="ts">
import { ref } from 'vue'
import { VButton, dismissSnackbar, snackbar } from 'vectis-ui'
const id = ref<number | null>(null)
function raise() {
// `snackbar` hands back an id, which is what lets this bar be taken away later and
// only this one: a stale id is ignored rather than closing whatever replaced it.
id.value = snackbar({ message: 'Stays until you take it away.', duration: 0 })
}
</script>
<template>
<!-- Raised into the <VSnackbar /> mounted once at the root of the application. -->
<div class="demo">
<!-- A duration of 0 disarms the countdown: the bar then stays until something
replaces it or takes it away. Reserve it for a confirmation the reader has to
act on, and give it a way out. -->
<VButton variant="outline" tone="neutral" @click="raise">Raise</VButton>
<VButton variant="outline" tone="neutral" @click="dismissSnackbar(id ?? undefined)">
Dismiss
</VButton>
</div>
</template>
<style scoped>
.demo {
display: flex;
flex-wrap: wrap;
gap: var(--vectis-space-3);
}
</style>
Which end of the bottom edge confirmations appear at, unless one of them asks for another.
duration
number
4000
How long a confirmation stays, in milliseconds, unless it asks for something else. A confirmation given 0 stays until it is replaced or taken away by hand.
actionText
string
none
The word drawn on the single action, when the confirmation does not give one. It is also the button's accessible name, and falls back to the design system dictionary.
label
string
none
What screen readers announce for the confirmation area itself, which is a landmark of the page. It falls back to the design system dictionary.