A short description of the element under the pointer, or under keyboard focus. It is a description and never a container: nothing inside it can be interacted with.
Usage
vue
Copy to the clipboard
<script setup lang="ts">
import { VButton, VTooltip } from 'vectis-ui'
</script>
<template>
<VTooltip text="Copy to the clipboard">
<template #default="{ triggerProps }">
<VButton variant="outline" tone="neutral" v-bind="triggerProps">Copy</VButton>
</template>
</VTooltip>
</template>
Examples
Placements
placement offers eight sides, four of them aligned to an edge of the trigger rather than centred on it. It names a preference: the browser moves the panel when the side asked for has no room.
vue
Placed top
Placed top-start
Placed top-end
Placed bottom
Placed bottom-start
Placed bottom-end
Placed left
Placed right
<script setup lang="ts">
import { VButton, VTooltip, type TooltipPlacement } from 'vectis-ui'
const placements: TooltipPlacement[] = [
'top',
'top-start',
'top-end',
'bottom',
'bottom-start',
'bottom-end',
'left',
'right',
]
</script>
<template>
<!-- Eight sides, four of them aligned to an edge of the trigger rather than centred
on it. The placement names a preference and not a position: with no room on that
side the browser moves the panel by itself. -->
<div class="row">
<VTooltip
v-for="placement in placements"
:key="placement"
:text="`Placed ${placement}`"
:placement="placement"
>
<template #default="{ triggerProps }">
<VButton variant="outline" tone="neutral" v-bind="triggerProps">{{ placement }}</VButton>
</template>
</VTooltip>
</div>
</template>
<style scoped>
.row {
display: flex;
flex-wrap: wrap;
gap: var(--vectis-space-6) var(--vectis-space-4);
/* Room above and below for the panels, which are drawn outside this box. */
padding-block: var(--vectis-space-8);
}
</style>
At the edge of the screen
With no room on the side it asked for, the panel takes the opposite one. It is pure CSS, with no measurement and no observer.
vue
Asks for the top, takes the bottom near the edge
Asks for the bottom, takes the top near the edge
<script setup lang="ts">
import { VButton, VTooltip } from 'vectis-ui'
</script>
<template>
<!-- The flip is pure CSS and costs no JavaScript: the panel names the side it would
rather be on, and the browser falls back to the opposite one when the viewport
has no room there. Scroll these buttons up against the top and the bottom of the
window to see it happen. -->
<div class="demo">
<VTooltip text="Asks for the top, takes the bottom near the edge" placement="top">
<template #default="{ triggerProps }">
<VButton variant="outline" tone="neutral" v-bind="triggerProps"> Prefers the top </VButton>
</template>
</VTooltip>
<VTooltip text="Asks for the bottom, takes the top near the edge" placement="bottom">
<template #default="{ triggerProps }">
<VButton variant="outline" tone="neutral" v-bind="triggerProps">
Prefers the bottom
</VButton>
</template>
</VTooltip>
</div>
</template>
<style scoped>
.demo {
display: flex;
flex-wrap: wrap;
gap: var(--vectis-space-4);
}
</style>
Opening and closing
delay is how long the pointer has to rest on the trigger before the tooltip appears, 300ms by default, and 0 removes the wait. Keyboard focus opens it at once, Escape closes it without taking the focus away, and pressing the trigger closes it too.
vue
Appears after the default wait
Appears after a longer wait
Appears at once
<script setup lang="ts">
import { VButton, VTooltip } from 'vectis-ui'
</script>
<template>
<div class="demo">
<!-- The pointer has to rest on the trigger before the tooltip appears, 300ms by
default, so passing over something on the way somewhere else opens nothing.
Keyboard focus opens it at once instead: the intent is not in doubt there. -->
<VTooltip text="Appears after the default wait">
<template #default="{ triggerProps }">
<VButton variant="outline" tone="neutral" v-bind="triggerProps">300ms</VButton>
</template>
</VTooltip>
<VTooltip text="Appears after a longer wait" :delay="800">
<template #default="{ triggerProps }">
<VButton variant="outline" tone="neutral" v-bind="triggerProps">800ms</VButton>
</template>
</VTooltip>
<!-- A delay of 0 disables the wait entirely. -->
<VTooltip text="Appears at once" :delay="0">
<template #default="{ triggerProps }">
<VButton variant="outline" tone="neutral" v-bind="triggerProps">No wait</VButton>
</template>
</VTooltip>
<!-- Tab to any of these and the tooltip opens immediately; Escape closes it
without taking the focus away, and pressing the trigger closes it too. -->
</div>
</template>
<style scoped>
.demo {
display: flex;
flex-wrap: wrap;
gap: var(--vectis-space-4);
}
</style>
Describing, not naming
The tooltip sets aria-describedby on its trigger, which keeps its own accessible name: an icon button still carries its label. A tap opens nothing, so whatever the tooltip says has to exist somewhere else as well.
vue
Search everything
Export the current view as a CSV file
Discard the draft
<script setup lang="ts">
import { VIconButton, VTooltip } from 'vectis-ui'
import { close, description, search } from 'vectis-ui/icons'
</script>
<template>
<div class="demo">
<!-- The tooltip DESCRIBES and never names: it sets `aria-describedby` on the
trigger, and the trigger keeps its own accessible name. An icon button
therefore still carries its `label`, and the tooltip is the visual
confirmation of it rather than a replacement. -->
<VTooltip text="Search everything">
<template #default="{ triggerProps }">
<VIconButton
v-bind="triggerProps"
:icon="search"
label="Search"
variant="outline"
tone="neutral"
/>
</template>
</VTooltip>
<VTooltip text="Export the current view as a CSV file">
<template #default="{ triggerProps }">
<VIconButton
v-bind="triggerProps"
:icon="description"
label="Export"
variant="outline"
tone="neutral"
/>
</template>
</VTooltip>
<!-- A tap opens nothing, there being no hover on a touch screen and no room for a
panel standing over what the tap just opened. So whatever the tooltip says has
to exist somewhere else too: a visible label, a field hint, a panel. -->
<VTooltip text="Discard the draft">
<template #default="{ triggerProps }">
<VIconButton
v-bind="triggerProps"
:icon="close"
label="Discard"
variant="outline"
tone="danger"
/>
</template>
</VTooltip>
</div>
</template>
<style scoped>
.demo {
display: flex;
flex-wrap: wrap;
gap: var(--vectis-space-4);
}
</style>
Rich content
The #content slot wins over the text prop when both are given. It has to stay non-interactive: nothing inside can be reached from the keyboard, and the description is flattened to plain text for a screen reader.
vue
Global searchLooks through projects, files and people. Shortcut Ctrl+KKeyboard shortcut: Ctrl + K
<script setup lang="ts">
import { VButton, VHotkeys, VTooltip, VTypography } from 'vectis-ui'
</script>
<template>
<!-- The slot wins over the `text` prop when both are given. It has to stay
non-interactive: the tooltip closes as soon as the pointer leaves the trigger,
so a link inside could never be reached, and the description is flattened to
plain text for a screen reader anyway. Anything one can act on belongs in a
panel that stays open. -->
<VTooltip placement="bottom-start">
<template #default="{ triggerProps }">
<VButton variant="outline" tone="neutral" v-bind="triggerProps">Search</VButton>
</template>
<template #content>
<div class="content">
<VTypography variant="label" as="strong">Global search</VTypography>
<span>Looks through projects, files and people.</span>
<!-- The panel is an inverse surface, dark in both themes, and VHotkeys paints
itself from the colour it inherits: the caps follow it with nothing to
set. -->
<span class="shortcut">
Shortcut
<VHotkeys keys="mod+k" variant="outline" />
</span>
</div>
</template>
</VTooltip>
</template>
<style scoped>
.content {
display: grid;
gap: var(--vectis-space-1);
padding-block: var(--vectis-space-1);
}
.shortcut {
display: flex;
align-items: center;
gap: var(--vectis-space-2);
}
</style>
API
Props
Prop
Type
Default
text
string
none
What the tooltip says. The #content slot replaces it when both are given.
Which side of the element the tooltip appears on. The browser flips it to the opposite side by itself when there is not enough room.
delay
number
300
How long the pointer must rest on the element before the tooltip appears, in milliseconds. Keyboard focus opens it at once, the intent not being in doubt there, and a delay of 0 disables the wait entirely.
The element the tooltip describes. Bind the triggerProps it receives onto it, which is what ties the two together for assistive technology, and make sure it is something that can take focus, or keyboard users will never see the tooltip.
content
{}
Content richer than a plain string: formatting, a keyboard shortcut, an icon. It must stay non-interactive: the description is flattened to plain text for screen readers, and nothing inside can be reached from the keyboard. Content one can interact with belongs in a panel that stays open, such as VMenu.
Types
The types the tables above name, written as the library declares them. The ones carrying export can be imported from vectis-ui to type your own code; the others are the shape of what a slot hands out.
export type TooltipTriggerProps = {
'aria-describedby': string
}