Tooltip
A small pop-up box that appears when a user hovers over an element
TailwindCSS
Alpine.js
Default
package showcase
import "github.com/axzilla/templui/pkg/components"
templ Tooltip() {
@components.Tooltip(components.TooltipProps{
Trigger: components.Button(components.ButtonProps{
Text: "Hover me",
Variant: components.ButtonVariantOutline,
}),
Content: templ.Raw("Default"),
})
}
package components
import (
"github.com/axzilla/templui/pkg/utils"
"strconv"
)
type TooltipSide string
const (
TooltipTop TooltipSide = "top"
TooltipRight TooltipSide = "right"
TooltipBottom TooltipSide = "bottom"
TooltipLeft TooltipSide = "left"
)
type TooltipVariant string
const (
TooltipDefault TooltipVariant = "default"
TooltipSecondary TooltipVariant = "secondary"
TooltipDestructive TooltipVariant = "destructive"
)
// TooltipProps configures the Tooltip component
type TooltipProps struct {
Trigger templ.Component // The element that triggers the tooltip
Content templ.Component // The tooltip content
Side TooltipSide // Tooltip position relative to trigger
ShowArrow bool // Whether to show the arrow pointer
OpenDelay int // Delay before showing tooltip (ms)
CloseDelay int // Delay before hiding tooltip (ms)
Variant TooltipVariant // Visual style variant
Class string // Additional CSS classes
Attributes templ.Attributes // Additional HTML attributes
}
templ TooltipScript() {
{{ handle := templ.NewOnceHandle() }}
@handle.Once() {
<script defer nonce={ templ.GetNonce(ctx) }>
document.addEventListener('alpine:init', () => {
Alpine.data('tooltip', () => ({
visible: false,
timeoutId: null, // Track the timeout
openDelay: null,
closeDelay: null,
init() {
this.openDelay = parseInt(this.$root.dataset.openDelay) || 0;
this.closeDelay = parseInt(this.$root.dataset.closeDelay) || 0;
},
show() {
// Clear any existing timeout first
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
this.timeoutId = setTimeout(() => {
this.visible = true;
this.timeoutId = null; // Clear the reference
}, this.openDelay);
},
hide() {
// Clear any existing timeout first
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
this.timeoutId = setTimeout(() => {
this.visible = false;
this.timeoutId = null; // Clear the reference
}, this.closeDelay);
},
handleMouseLeave() {
this.hide();
}
}));
});
</script>
}
}
func getTooltipSideClass(side TooltipSide) string {
switch side {
case TooltipTop:
return "bottom-full left-1/2 -translate-x-1/2 mb-2"
case TooltipRight:
return "left-full top-1/2 -translate-y-1/2 ml-2"
case TooltipBottom:
return "top-full left-1/2 -translate-x-1/2 mt-2"
case TooltipLeft:
return "right-full top-1/2 -translate-y-1/2 mr-2"
default:
return "bottom-full left-1/2 -translate-x-1/2 mb-2"
}
}
func getTooltipVariantClass(variant TooltipVariant) string {
switch variant {
case TooltipDefault:
return "bg-bg text-primary-foreground"
case TooltipSecondary:
return "bg-secondary text-secondary-foreground"
case TooltipDestructive:
return "bg-destructive text-destructive-foreground"
default:
return "bg-foreground text-background"
}
}
func getArrowClass(side TooltipSide) string {
switch side {
case TooltipTop:
return "bottom-[-4px] left-1/2 -translate-x-1/2"
case TooltipRight:
return "left-[-4px] top-1/2 -translate-y-1/2"
case TooltipBottom:
return "top-[-4px] left-1/2 -translate-x-1/2"
case TooltipLeft:
return "right-[-4px] top-1/2 -translate-y-1/2"
default:
return "bottom-[-4px] left-1/2 -translate-x-1/2"
}
}
func getArrowColor(variant TooltipVariant) string {
switch variant {
case TooltipDefault:
return "bg-foreground"
case TooltipSecondary:
return "bg-secondary"
case TooltipDestructive:
return "bg-destructive"
default:
return "bg-foreground"
}
}
// A small pop-up box that appears when a user hovers over an element
templ Tooltip(props TooltipProps) {
<div
x-data="tooltip"
if props.OpenDelay > 0 {
data-open-delay={ strconv.Itoa(props.OpenDelay) }
}
if props.CloseDelay > 0 {
data-close-delay={ strconv.Itoa(props.CloseDelay) }
}
class="relative inline-block"
{ props.Attributes... }
>
<!-- Trigger -->
<div
@mouseenter="show"
@mouseleave="handleMouseLeave"
@focus="show"
@blur="hide"
>
@props.Trigger
</div>
<!-- Tooltip -->
<div
x-show="visible"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-100"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
@mouseenter="show"
@mouseleave="hide"
class={
utils.TwMerge(
// Layout
"absolute w-auto z-50",
"flex-shrink-1 block whitespace-nowrap",
// Style
"text-xs",
"px-4 py-1 rounded-lg",
// Conditional
getTooltipSideClass(props.Side),
getTooltipVariantClass(props.Variant),
// Custom
props.Class,
),
}
>
<!-- Arrow -->
if props.ShowArrow {
<div
class={
utils.TwMerge(
// Layout
"absolute h-2 w-2",
// Style
"rotate-45",
// Conditional
getArrowClass(props.Side),
getArrowColor(props.Variant),
),
}
></div>
}
<!-- Content -->
@props.Content
</div>
</div>
}
Usage
1. Add the script to your page/layout:
// Option A: All components (recommended)
@utils.ComponentScripts()
// Option B: Just Tooltip
@components.TooltipScript()
2. Use the component:
@components.Tooltip(components.TooltipProps{...})
Examples
With delay (3s open, 1s close)
With delay
package showcase
import "github.com/axzilla/templui/pkg/components"
templ TooltipWithDelay() {
@components.Tooltip(components.TooltipProps{
Trigger: components.Button(components.ButtonProps{
Text: "Hover me",
Variant: components.ButtonVariantOutline,
}),
Content: templ.Raw("With delay"),
OpenDelay: 3000,
CloseDelay: 1000,
})
}
package components
import (
"github.com/axzilla/templui/pkg/utils"
"strconv"
)
type TooltipSide string
const (
TooltipTop TooltipSide = "top"
TooltipRight TooltipSide = "right"
TooltipBottom TooltipSide = "bottom"
TooltipLeft TooltipSide = "left"
)
type TooltipVariant string
const (
TooltipDefault TooltipVariant = "default"
TooltipSecondary TooltipVariant = "secondary"
TooltipDestructive TooltipVariant = "destructive"
)
// TooltipProps configures the Tooltip component
type TooltipProps struct {
Trigger templ.Component // The element that triggers the tooltip
Content templ.Component // The tooltip content
Side TooltipSide // Tooltip position relative to trigger
ShowArrow bool // Whether to show the arrow pointer
OpenDelay int // Delay before showing tooltip (ms)
CloseDelay int // Delay before hiding tooltip (ms)
Variant TooltipVariant // Visual style variant
Class string // Additional CSS classes
Attributes templ.Attributes // Additional HTML attributes
}
templ TooltipScript() {
{{ handle := templ.NewOnceHandle() }}
@handle.Once() {
<script defer nonce={ templ.GetNonce(ctx) }>
document.addEventListener('alpine:init', () => {
Alpine.data('tooltip', () => ({
visible: false,
timeoutId: null, // Track the timeout
openDelay: null,
closeDelay: null,
init() {
this.openDelay = parseInt(this.$root.dataset.openDelay) || 0;
this.closeDelay = parseInt(this.$root.dataset.closeDelay) || 0;
},
show() {
// Clear any existing timeout first
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
this.timeoutId = setTimeout(() => {
this.visible = true;
this.timeoutId = null; // Clear the reference
}, this.openDelay);
},
hide() {
// Clear any existing timeout first
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
this.timeoutId = setTimeout(() => {
this.visible = false;
this.timeoutId = null; // Clear the reference
}, this.closeDelay);
},
handleMouseLeave() {
this.hide();
}
}));
});
</script>
}
}
func getTooltipSideClass(side TooltipSide) string {
switch side {
case TooltipTop:
return "bottom-full left-1/2 -translate-x-1/2 mb-2"
case TooltipRight:
return "left-full top-1/2 -translate-y-1/2 ml-2"
case TooltipBottom:
return "top-full left-1/2 -translate-x-1/2 mt-2"
case TooltipLeft:
return "right-full top-1/2 -translate-y-1/2 mr-2"
default:
return "bottom-full left-1/2 -translate-x-1/2 mb-2"
}
}
func getTooltipVariantClass(variant TooltipVariant) string {
switch variant {
case TooltipDefault:
return "bg-bg text-primary-foreground"
case TooltipSecondary:
return "bg-secondary text-secondary-foreground"
case TooltipDestructive:
return "bg-destructive text-destructive-foreground"
default:
return "bg-foreground text-background"
}
}
func getArrowClass(side TooltipSide) string {
switch side {
case TooltipTop:
return "bottom-[-4px] left-1/2 -translate-x-1/2"
case TooltipRight:
return "left-[-4px] top-1/2 -translate-y-1/2"
case TooltipBottom:
return "top-[-4px] left-1/2 -translate-x-1/2"
case TooltipLeft:
return "right-[-4px] top-1/2 -translate-y-1/2"
default:
return "bottom-[-4px] left-1/2 -translate-x-1/2"
}
}
func getArrowColor(variant TooltipVariant) string {
switch variant {
case TooltipDefault:
return "bg-foreground"
case TooltipSecondary:
return "bg-secondary"
case TooltipDestructive:
return "bg-destructive"
default:
return "bg-foreground"
}
}
// A small pop-up box that appears when a user hovers over an element
templ Tooltip(props TooltipProps) {
<div
x-data="tooltip"
if props.OpenDelay > 0 {
data-open-delay={ strconv.Itoa(props.OpenDelay) }
}
if props.CloseDelay > 0 {
data-close-delay={ strconv.Itoa(props.CloseDelay) }
}
class="relative inline-block"
{ props.Attributes... }
>
<!-- Trigger -->
<div
@mouseenter="show"
@mouseleave="handleMouseLeave"
@focus="show"
@blur="hide"
>
@props.Trigger
</div>
<!-- Tooltip -->
<div
x-show="visible"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-100"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
@mouseenter="show"
@mouseleave="hide"
class={
utils.TwMerge(
// Layout
"absolute w-auto z-50",
"flex-shrink-1 block whitespace-nowrap",
// Style
"text-xs",
"px-4 py-1 rounded-lg",
// Conditional
getTooltipSideClass(props.Side),
getTooltipVariantClass(props.Variant),
// Custom
props.Class,
),
}
>
<!-- Arrow -->
if props.ShowArrow {
<div
class={
utils.TwMerge(
// Layout
"absolute h-2 w-2",
// Style
"rotate-45",
// Conditional
getArrowClass(props.Side),
getArrowColor(props.Variant),
),
}
></div>
}
<!-- Content -->
@props.Content
</div>
</div>
}
Variants
Default tooltip
Secondary tooltip
Destructive tooltip
package showcase
import "github.com/axzilla/templui/pkg/components"
templ TooltipVariants() {
<div class="flex flex-wrap gap-2">
@components.Tooltip(components.TooltipProps{
Trigger: components.Button(components.ButtonProps{
Text: "Default",
Variant: components.ButtonVariantOutline,
}),
Content: templ.Raw("Default tooltip"),
})
@components.Tooltip(components.TooltipProps{
Trigger: components.Button(components.ButtonProps{
Text: "Secondary",
Variant: components.ButtonVariantOutline,
}),
Content: templ.Raw("Secondary tooltip"),
Variant: components.TooltipSecondary,
})
@components.Tooltip(components.TooltipProps{
Trigger: components.Button(components.ButtonProps{
Text: "Destructive",
Variant: components.ButtonVariantOutline,
}),
Content: templ.Raw("Destructive tooltip"),
Variant: components.TooltipDestructive,
})
</div>
}
package components
import (
"github.com/axzilla/templui/pkg/utils"
"strconv"
)
type TooltipSide string
const (
TooltipTop TooltipSide = "top"
TooltipRight TooltipSide = "right"
TooltipBottom TooltipSide = "bottom"
TooltipLeft TooltipSide = "left"
)
type TooltipVariant string
const (
TooltipDefault TooltipVariant = "default"
TooltipSecondary TooltipVariant = "secondary"
TooltipDestructive TooltipVariant = "destructive"
)
// TooltipProps configures the Tooltip component
type TooltipProps struct {
Trigger templ.Component // The element that triggers the tooltip
Content templ.Component // The tooltip content
Side TooltipSide // Tooltip position relative to trigger
ShowArrow bool // Whether to show the arrow pointer
OpenDelay int // Delay before showing tooltip (ms)
CloseDelay int // Delay before hiding tooltip (ms)
Variant TooltipVariant // Visual style variant
Class string // Additional CSS classes
Attributes templ.Attributes // Additional HTML attributes
}
templ TooltipScript() {
{{ handle := templ.NewOnceHandle() }}
@handle.Once() {
<script defer nonce={ templ.GetNonce(ctx) }>
document.addEventListener('alpine:init', () => {
Alpine.data('tooltip', () => ({
visible: false,
timeoutId: null, // Track the timeout
openDelay: null,
closeDelay: null,
init() {
this.openDelay = parseInt(this.$root.dataset.openDelay) || 0;
this.closeDelay = parseInt(this.$root.dataset.closeDelay) || 0;
},
show() {
// Clear any existing timeout first
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
this.timeoutId = setTimeout(() => {
this.visible = true;
this.timeoutId = null; // Clear the reference
}, this.openDelay);
},
hide() {
// Clear any existing timeout first
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
this.timeoutId = setTimeout(() => {
this.visible = false;
this.timeoutId = null; // Clear the reference
}, this.closeDelay);
},
handleMouseLeave() {
this.hide();
}
}));
});
</script>
}
}
func getTooltipSideClass(side TooltipSide) string {
switch side {
case TooltipTop:
return "bottom-full left-1/2 -translate-x-1/2 mb-2"
case TooltipRight:
return "left-full top-1/2 -translate-y-1/2 ml-2"
case TooltipBottom:
return "top-full left-1/2 -translate-x-1/2 mt-2"
case TooltipLeft:
return "right-full top-1/2 -translate-y-1/2 mr-2"
default:
return "bottom-full left-1/2 -translate-x-1/2 mb-2"
}
}
func getTooltipVariantClass(variant TooltipVariant) string {
switch variant {
case TooltipDefault:
return "bg-bg text-primary-foreground"
case TooltipSecondary:
return "bg-secondary text-secondary-foreground"
case TooltipDestructive:
return "bg-destructive text-destructive-foreground"
default:
return "bg-foreground text-background"
}
}
func getArrowClass(side TooltipSide) string {
switch side {
case TooltipTop:
return "bottom-[-4px] left-1/2 -translate-x-1/2"
case TooltipRight:
return "left-[-4px] top-1/2 -translate-y-1/2"
case TooltipBottom:
return "top-[-4px] left-1/2 -translate-x-1/2"
case TooltipLeft:
return "right-[-4px] top-1/2 -translate-y-1/2"
default:
return "bottom-[-4px] left-1/2 -translate-x-1/2"
}
}
func getArrowColor(variant TooltipVariant) string {
switch variant {
case TooltipDefault:
return "bg-foreground"
case TooltipSecondary:
return "bg-secondary"
case TooltipDestructive:
return "bg-destructive"
default:
return "bg-foreground"
}
}
// A small pop-up box that appears when a user hovers over an element
templ Tooltip(props TooltipProps) {
<div
x-data="tooltip"
if props.OpenDelay > 0 {
data-open-delay={ strconv.Itoa(props.OpenDelay) }
}
if props.CloseDelay > 0 {
data-close-delay={ strconv.Itoa(props.CloseDelay) }
}
class="relative inline-block"
{ props.Attributes... }
>
<!-- Trigger -->
<div
@mouseenter="show"
@mouseleave="handleMouseLeave"
@focus="show"
@blur="hide"
>
@props.Trigger
</div>
<!-- Tooltip -->
<div
x-show="visible"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-100"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
@mouseenter="show"
@mouseleave="hide"
class={
utils.TwMerge(
// Layout
"absolute w-auto z-50",
"flex-shrink-1 block whitespace-nowrap",
// Style
"text-xs",
"px-4 py-1 rounded-lg",
// Conditional
getTooltipSideClass(props.Side),
getTooltipVariantClass(props.Variant),
// Custom
props.Class,
),
}
>
<!-- Arrow -->
if props.ShowArrow {
<div
class={
utils.TwMerge(
// Layout
"absolute h-2 w-2",
// Style
"rotate-45",
// Conditional
getArrowClass(props.Side),
getArrowColor(props.Variant),
),
}
></div>
}
<!-- Content -->
@props.Content
</div>
</div>
}
Sides
Top tooltip
Right tooltip
Bottom tooltip
Left tooltip
package showcase
import "github.com/axzilla/templui/pkg/components"
templ TooltipSides() {
<div class="flex flex-wrap gap-2">
@components.Tooltip(components.TooltipProps{
Trigger: components.Button(components.ButtonProps{
Text: "Top",
Variant: components.ButtonVariantOutline,
}),
Content: templ.Raw("Top tooltip"),
Side: components.TooltipTop, // Its the default value but we can set it explicitly
})
@components.Tooltip(components.TooltipProps{
Trigger: components.Button(components.ButtonProps{
Text: "Right",
Variant: components.ButtonVariantOutline,
}),
Content: templ.Raw("Right tooltip"),
Side: components.TooltipRight,
})
@components.Tooltip(components.TooltipProps{
Trigger: components.Button(components.ButtonProps{
Text: "Bottom",
Variant: components.ButtonVariantOutline,
}),
Content: templ.Raw("Bottom tooltip"),
Side: components.TooltipBottom,
})
@components.Tooltip(components.TooltipProps{
Trigger: components.Button(components.ButtonProps{
Text: "Left",
Variant: components.ButtonVariantOutline,
}),
Content: templ.Raw("Left tooltip"),
Side: components.TooltipLeft,
})
</div>
}
package components
import (
"github.com/axzilla/templui/pkg/utils"
"strconv"
)
type TooltipSide string
const (
TooltipTop TooltipSide = "top"
TooltipRight TooltipSide = "right"
TooltipBottom TooltipSide = "bottom"
TooltipLeft TooltipSide = "left"
)
type TooltipVariant string
const (
TooltipDefault TooltipVariant = "default"
TooltipSecondary TooltipVariant = "secondary"
TooltipDestructive TooltipVariant = "destructive"
)
// TooltipProps configures the Tooltip component
type TooltipProps struct {
Trigger templ.Component // The element that triggers the tooltip
Content templ.Component // The tooltip content
Side TooltipSide // Tooltip position relative to trigger
ShowArrow bool // Whether to show the arrow pointer
OpenDelay int // Delay before showing tooltip (ms)
CloseDelay int // Delay before hiding tooltip (ms)
Variant TooltipVariant // Visual style variant
Class string // Additional CSS classes
Attributes templ.Attributes // Additional HTML attributes
}
templ TooltipScript() {
{{ handle := templ.NewOnceHandle() }}
@handle.Once() {
<script defer nonce={ templ.GetNonce(ctx) }>
document.addEventListener('alpine:init', () => {
Alpine.data('tooltip', () => ({
visible: false,
timeoutId: null, // Track the timeout
openDelay: null,
closeDelay: null,
init() {
this.openDelay = parseInt(this.$root.dataset.openDelay) || 0;
this.closeDelay = parseInt(this.$root.dataset.closeDelay) || 0;
},
show() {
// Clear any existing timeout first
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
this.timeoutId = setTimeout(() => {
this.visible = true;
this.timeoutId = null; // Clear the reference
}, this.openDelay);
},
hide() {
// Clear any existing timeout first
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
this.timeoutId = setTimeout(() => {
this.visible = false;
this.timeoutId = null; // Clear the reference
}, this.closeDelay);
},
handleMouseLeave() {
this.hide();
}
}));
});
</script>
}
}
func getTooltipSideClass(side TooltipSide) string {
switch side {
case TooltipTop:
return "bottom-full left-1/2 -translate-x-1/2 mb-2"
case TooltipRight:
return "left-full top-1/2 -translate-y-1/2 ml-2"
case TooltipBottom:
return "top-full left-1/2 -translate-x-1/2 mt-2"
case TooltipLeft:
return "right-full top-1/2 -translate-y-1/2 mr-2"
default:
return "bottom-full left-1/2 -translate-x-1/2 mb-2"
}
}
func getTooltipVariantClass(variant TooltipVariant) string {
switch variant {
case TooltipDefault:
return "bg-bg text-primary-foreground"
case TooltipSecondary:
return "bg-secondary text-secondary-foreground"
case TooltipDestructive:
return "bg-destructive text-destructive-foreground"
default:
return "bg-foreground text-background"
}
}
func getArrowClass(side TooltipSide) string {
switch side {
case TooltipTop:
return "bottom-[-4px] left-1/2 -translate-x-1/2"
case TooltipRight:
return "left-[-4px] top-1/2 -translate-y-1/2"
case TooltipBottom:
return "top-[-4px] left-1/2 -translate-x-1/2"
case TooltipLeft:
return "right-[-4px] top-1/2 -translate-y-1/2"
default:
return "bottom-[-4px] left-1/2 -translate-x-1/2"
}
}
func getArrowColor(variant TooltipVariant) string {
switch variant {
case TooltipDefault:
return "bg-foreground"
case TooltipSecondary:
return "bg-secondary"
case TooltipDestructive:
return "bg-destructive"
default:
return "bg-foreground"
}
}
// A small pop-up box that appears when a user hovers over an element
templ Tooltip(props TooltipProps) {
<div
x-data="tooltip"
if props.OpenDelay > 0 {
data-open-delay={ strconv.Itoa(props.OpenDelay) }
}
if props.CloseDelay > 0 {
data-close-delay={ strconv.Itoa(props.CloseDelay) }
}
class="relative inline-block"
{ props.Attributes... }
>
<!-- Trigger -->
<div
@mouseenter="show"
@mouseleave="handleMouseLeave"
@focus="show"
@blur="hide"
>
@props.Trigger
</div>
<!-- Tooltip -->
<div
x-show="visible"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-100"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
@mouseenter="show"
@mouseleave="hide"
class={
utils.TwMerge(
// Layout
"absolute w-auto z-50",
"flex-shrink-1 block whitespace-nowrap",
// Style
"text-xs",
"px-4 py-1 rounded-lg",
// Conditional
getTooltipSideClass(props.Side),
getTooltipVariantClass(props.Variant),
// Custom
props.Class,
),
}
>
<!-- Arrow -->
if props.ShowArrow {
<div
class={
utils.TwMerge(
// Layout
"absolute h-2 w-2",
// Style
"rotate-45",
// Conditional
getArrowClass(props.Side),
getArrowColor(props.Variant),
),
}
></div>
}
<!-- Content -->
@props.Content
</div>
</div>
}
With Arrow
Top tooltip
Right tooltip
Bottom tooltip
Left tooltip
package showcase
import "github.com/axzilla/templui/pkg/components"
templ TooltipWithArrow() {
<div class="flex flex-wrap gap-2">
@components.Tooltip(components.TooltipProps{
Trigger: components.Button(components.ButtonProps{
Text: "Top",
Variant: components.ButtonVariantOutline,
}),
Content: templ.Raw("Top tooltip"),
Side: components.TooltipTop, // Its the default value but we can set it explicitly
ShowArrow: true,
})
@components.Tooltip(components.TooltipProps{
Trigger: components.Button(components.ButtonProps{
Text: "Right",
Variant: components.ButtonVariantOutline,
}),
Content: templ.Raw("Right tooltip"),
Side: components.TooltipRight,
ShowArrow: true,
})
@components.Tooltip(components.TooltipProps{
Trigger: components.Button(components.ButtonProps{
Text: "Bottom",
Variant: components.ButtonVariantOutline,
}),
Content: templ.Raw("Bottom tooltip"),
Side: components.TooltipBottom,
ShowArrow: true,
})
@components.Tooltip(components.TooltipProps{
Trigger: components.Button(components.ButtonProps{
Text: "Left",
Variant: components.ButtonVariantOutline,
}),
Content: templ.Raw("Left tooltip"),
Side: components.TooltipLeft,
ShowArrow: true,
})
</div>
}
package components
import (
"github.com/axzilla/templui/pkg/utils"
"strconv"
)
type TooltipSide string
const (
TooltipTop TooltipSide = "top"
TooltipRight TooltipSide = "right"
TooltipBottom TooltipSide = "bottom"
TooltipLeft TooltipSide = "left"
)
type TooltipVariant string
const (
TooltipDefault TooltipVariant = "default"
TooltipSecondary TooltipVariant = "secondary"
TooltipDestructive TooltipVariant = "destructive"
)
// TooltipProps configures the Tooltip component
type TooltipProps struct {
Trigger templ.Component // The element that triggers the tooltip
Content templ.Component // The tooltip content
Side TooltipSide // Tooltip position relative to trigger
ShowArrow bool // Whether to show the arrow pointer
OpenDelay int // Delay before showing tooltip (ms)
CloseDelay int // Delay before hiding tooltip (ms)
Variant TooltipVariant // Visual style variant
Class string // Additional CSS classes
Attributes templ.Attributes // Additional HTML attributes
}
templ TooltipScript() {
{{ handle := templ.NewOnceHandle() }}
@handle.Once() {
<script defer nonce={ templ.GetNonce(ctx) }>
document.addEventListener('alpine:init', () => {
Alpine.data('tooltip', () => ({
visible: false,
timeoutId: null, // Track the timeout
openDelay: null,
closeDelay: null,
init() {
this.openDelay = parseInt(this.$root.dataset.openDelay) || 0;
this.closeDelay = parseInt(this.$root.dataset.closeDelay) || 0;
},
show() {
// Clear any existing timeout first
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
this.timeoutId = setTimeout(() => {
this.visible = true;
this.timeoutId = null; // Clear the reference
}, this.openDelay);
},
hide() {
// Clear any existing timeout first
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
this.timeoutId = setTimeout(() => {
this.visible = false;
this.timeoutId = null; // Clear the reference
}, this.closeDelay);
},
handleMouseLeave() {
this.hide();
}
}));
});
</script>
}
}
func getTooltipSideClass(side TooltipSide) string {
switch side {
case TooltipTop:
return "bottom-full left-1/2 -translate-x-1/2 mb-2"
case TooltipRight:
return "left-full top-1/2 -translate-y-1/2 ml-2"
case TooltipBottom:
return "top-full left-1/2 -translate-x-1/2 mt-2"
case TooltipLeft:
return "right-full top-1/2 -translate-y-1/2 mr-2"
default:
return "bottom-full left-1/2 -translate-x-1/2 mb-2"
}
}
func getTooltipVariantClass(variant TooltipVariant) string {
switch variant {
case TooltipDefault:
return "bg-bg text-primary-foreground"
case TooltipSecondary:
return "bg-secondary text-secondary-foreground"
case TooltipDestructive:
return "bg-destructive text-destructive-foreground"
default:
return "bg-foreground text-background"
}
}
func getArrowClass(side TooltipSide) string {
switch side {
case TooltipTop:
return "bottom-[-4px] left-1/2 -translate-x-1/2"
case TooltipRight:
return "left-[-4px] top-1/2 -translate-y-1/2"
case TooltipBottom:
return "top-[-4px] left-1/2 -translate-x-1/2"
case TooltipLeft:
return "right-[-4px] top-1/2 -translate-y-1/2"
default:
return "bottom-[-4px] left-1/2 -translate-x-1/2"
}
}
func getArrowColor(variant TooltipVariant) string {
switch variant {
case TooltipDefault:
return "bg-foreground"
case TooltipSecondary:
return "bg-secondary"
case TooltipDestructive:
return "bg-destructive"
default:
return "bg-foreground"
}
}
// A small pop-up box that appears when a user hovers over an element
templ Tooltip(props TooltipProps) {
<div
x-data="tooltip"
if props.OpenDelay > 0 {
data-open-delay={ strconv.Itoa(props.OpenDelay) }
}
if props.CloseDelay > 0 {
data-close-delay={ strconv.Itoa(props.CloseDelay) }
}
class="relative inline-block"
{ props.Attributes... }
>
<!-- Trigger -->
<div
@mouseenter="show"
@mouseleave="handleMouseLeave"
@focus="show"
@blur="hide"
>
@props.Trigger
</div>
<!-- Tooltip -->
<div
x-show="visible"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-100"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
@mouseenter="show"
@mouseleave="hide"
class={
utils.TwMerge(
// Layout
"absolute w-auto z-50",
"flex-shrink-1 block whitespace-nowrap",
// Style
"text-xs",
"px-4 py-1 rounded-lg",
// Conditional
getTooltipSideClass(props.Side),
getTooltipVariantClass(props.Variant),
// Custom
props.Class,
),
}
>
<!-- Arrow -->
if props.ShowArrow {
<div
class={
utils.TwMerge(
// Layout
"absolute h-2 w-2",
// Style
"rotate-45",
// Conditional
getArrowClass(props.Side),
getArrowColor(props.Variant),
),
}
></div>
}
<!-- Content -->
@props.Content
</div>
</div>
}