Stateful LiveComponent for rendering a terminal in the browser.
Handles keyboard events internally — no handle_event wiring
needed in the parent LiveView. The parent owns the terminal
and optionally a PTY; this component owns the UI interaction.
Usage
# In your LiveView mount:
{:ok, term} = Ghostty.Terminal.start_link(cols: 80, rows: 24)
{:ok, pty} = Ghostty.PTY.start_link(cmd: "/bin/bash", cols: 80, rows: 24)
{:ok, assign(socket, term: term, pty: pty)}
# In your LiveView template:
<.live_component
module={Ghostty.LiveTerminal.Component}
id="term"
term={@term}
pty={@pty}
/>
# Forward PTY output to the terminal and trigger a refresh:
def handle_info({:data, data}, socket) do
Ghostty.Terminal.write(socket.assigns.term, data)
send_update(Ghostty.LiveTerminal.Component, id: "term", refresh: true)
{:noreply, socket}
endAssigns
:id— required:term— required, aGhostty.Terminalpid:pty— optional, aGhostty.PTYpid; key input is written here when present:cols— terminal width (default:80):rows— terminal height (default:24):fit— auto-fit terminal size to the rendered container (default:false):autofocus— focus the hidden terminal input on mount (default:false):class— CSS class for the container div (default:"")
Global HTML attributes (data-*, aria-*, etc.) are passed through.
Refreshing
To push updated cells after writing to the terminal from the parent,
use send_update/3:
Ghostty.Terminal.write(socket.assigns.term, data)
send_update(Ghostty.LiveTerminal.Component, id: "term", refresh: true)The component also pushes an initial render automatically when the LiveView socket is connected.