Documentation Index Fetch the complete documentation index at: https://mintlify.com/charmbracelet/bubbletea/llms.txt
Use this file to discover all available pages before exploring further.
Bubble Tea supports full mouse interaction including clicks, drags, and motion events. This guide shows you how to handle mouse input in your applications.
Basic Mouse Events
The simplest mouse example from examples/mouse/main.go shows how to capture mouse coordinates:
package main
import (
" log "
tea " charm.land/bubbletea/v2 "
)
type model struct {}
func ( m model ) Init () tea . Cmd {
return nil
}
func ( m model ) Update ( msg tea . Msg ) ( tea . Model , tea . Cmd ) {
switch msg := msg .( type ) {
case tea . KeyPressMsg :
if s := msg . String (); s == "ctrl+c" || s == "q" || s == "esc" {
return m , tea . Quit
}
case tea . MouseMsg :
mouse := msg . Mouse ()
return m , tea . Printf ( "(X: %d , Y: %d ) %s " , mouse . X , mouse . Y , mouse )
}
return m , nil
}
func ( m model ) View () tea . View {
v := tea . NewView ( "Do mouse stuff. When you're done press q to quit. \n " )
v . MouseMode = tea . MouseModeAllMotion
return v
}
func main () {
p := tea . NewProgram ( model {})
if _ , err := p . Run (); err != nil {
log . Fatal ( err )
}
}
Key Concepts
Enabling Mouse Mode
Set the mouse mode in your View() method:
func ( m model ) View () tea . View {
v := tea . NewView ( "..." )
v . MouseMode = tea . MouseModeAllMotion
return v
}
Available mouse modes:
tea.MouseModeNone - Disable mouse events
tea.MouseModeClick - Only click events
tea.MouseModeMotion - Clicks and motion when button pressed
tea.MouseModeAllMotion - All mouse events including hover
Handling Mouse Messages
Mouse events are delivered as tea.MouseMsg:
case tea . MouseMsg :
mouse := msg . Mouse ()
x := mouse . X
y := mouse . Y
button := mouse . Button
Mouse Message Types
Check the specific mouse event type:
switch msg := msg .( type ) {
case tea . MouseClickMsg :
// Button was pressed
if mouse . Button == tea . MouseLeft {
// Handle left click
}
case tea . MouseMotionMsg :
// Mouse moved with button pressed (drag)
case tea . MouseReleaseMsg :
// Button was released
}
Advanced: Clickable Interface
The examples/clickable directory contains a sophisticated example with draggable dialog boxes. Here are the key patterns:
Layer-Based Hit Detection
Use Lip Gloss layers to detect which element was clicked:
type LayerHitMsg struct {
ID string
Mouse tea . MouseMsg
}
func ( m model ) View () tea . View {
var v tea . View
// Create layers for clickable elements
root := lipgloss . NewLayer ( bg ). ID ( "bg" )
for i , d := range m . dialogs {
root . AddLayers ( d . view (). Z ( i + 1 ))
}
comp := lipgloss . NewCompositor ( root )
// Set up mouse handling
v . MouseMode = tea . MouseModeAllMotion
v . OnMouse = func ( msg tea . MouseMsg ) tea . Cmd {
return func () tea . Msg {
mouse := msg . Mouse ()
x , y := mouse . X , mouse . Y
if id := comp . Hit ( x , y ). ID (); id != "" {
return LayerHitMsg {
ID : id ,
Mouse : msg ,
}
}
return nil
}
}
v . SetContent ( comp . Render ())
return v
}
Drag and Drop Implementation
Track mouse state for drag operations:
type model struct {
width , height int
dialogs [] dialog
mouseDown bool
pressID string
dragID string
dragOffsetX int
dragOffsetY int
}
func ( m model ) Update ( msg tea . Msg ) ( tea . Model , tea . Cmd ) {
switch msg := msg .( type ) {
case LayerHitMsg :
mouse := msg . Mouse . Mouse ()
switch msg . Mouse .( type ) {
case tea . MouseClickMsg :
if mouse . Button != tea . MouseLeft {
break
}
// Initial press
if ! m . mouseDown {
m . mouseDown = true
m . pressID = msg . ID
// Find clicked dialog and init drag
for i , d := range m . dialogs {
if d . id != msg . ID {
continue
}
m . dragID = msg . ID
m . dragOffsetX = mouse . X - d . x
m . dragOffsetY = mouse . Y - d . y
break
}
}
case tea . MouseMotionMsg :
// Dragging
if m . mouseDown && m . dragID != "" {
for i := range m . dialogs {
d := & m . dialogs [ i ]
if d . id != m . dragID {
continue
}
// Move dialog with cursor
d . x = clamp ( mouse . X - ( m . dragOffsetX ), 0 , m . width - lipgloss . Width ( d . windowView ()))
d . y = clamp ( mouse . Y - ( m . dragOffsetY ), 0 , m . height - lipgloss . Height ( d . windowView ()))
break
}
}
case tea . MouseReleaseMsg :
// Complete click if press and release on same element
if m . pressID == msg . ID {
// Handle click
}
m . mouseDown = false
m . dragID = ""
m . pressID = ""
}
}
return m , nil
}
Hover States
Track which element is being hovered:
case tea . MouseMotionMsg :
// Update hover state
for i := range m . dialogs {
d := & m . dialogs [ i ]
d . hovering = false
d . hoveringButton = false
if d . id == msg . ID {
d . hovering = true
continue
}
if d . buttonID == msg . ID {
d . hovering = true
d . hoveringButton = true
continue
}
}
Z-Index Management
Bring clicked elements to front:
// Move clicked dialog to end of slice (highest z-index)
m . dialogs = m . removeDialog ( i )
m . dialogs = append ( m . dialogs , d )
switch mouse . Button {
case tea . MouseLeft :
// Left button
case tea . MouseRight :
// Right button
case tea . MouseMiddle :
// Middle button
case tea . MouseWheelUp :
// Scroll up
case tea . MouseWheelDown :
// Scroll down
}
Common Patterns
Click Detection
type model struct {
pressID string
mouseDown bool
}
func ( m model ) Update ( msg tea . Msg ) ( tea . Model , tea . Cmd ) {
switch msg := msg .( type ) {
case tea . MouseClickMsg :
m . mouseDown = true
m . pressID = getIDAtPosition ( msg . Mouse (). X , msg . Mouse (). Y )
case tea . MouseReleaseMsg :
releaseID := getIDAtPosition ( msg . Mouse (). X , msg . Mouse (). Y )
if m . mouseDown && m . pressID == releaseID && releaseID != "" {
// Valid click: pressed and released on same element
m . handleClick ( releaseID )
}
m . mouseDown = false
m . pressID = ""
}
return m , nil
}
Region-Based Detection
type clickableRegion struct {
x , y , width , height int
id string
}
func ( r clickableRegion ) contains ( x , y int ) bool {
return x >= r . x && x < r . x + r . width &&
y >= r . y && y < r . y + r . height
}
func ( m model ) getRegionAt ( x , y int ) string {
for _ , region := range m . regions {
if region . contains ( x , y ) {
return region . id
}
}
return ""
}
Best Practices
Use Layers Use Lip Gloss layers and compositor for accurate hit detection
Track State Maintain mouseDown state to distinguish clicks from releases
Visual Feedback Show hover and active states for better UX
Bounds Checking Clamp positions when dragging to keep elements on screen
Running the Examples
# Basic mouse events
cd examples/mouse
go run .
# Advanced clickable interface
cd examples/clickable
go run .
The clickable example lets you:
Click background to spawn dialog boxes
Drag dialogs to move them
Click buttons to close dialogs
See hover effects
Source Code