Skip to main content

Textual Textarea

Textual Textarea Screenshot

Note: This is NOT the official TextArea widget!

With v0.38.0, Textual added a built-in TextArea widget. You probably want to use that widget instead of this one. This project predated the official widget; versions < v0.8.0 had a completely separate implmentation.

Since v0.8.0, this project uses the built-in TextArea widget, but adds the features outlined below.

Installation

pip install textual-textarea

Features

Full-featured text editor experience with VS-Code-like bindings, in your Textual App:

  • Syntax highlighting and support for Pygments themes.
  • Move cursor and scroll with mouse or keys (including ctrl+arrow, PgUp/Dn, ctrl+Home/End).
  • Open (ctrl+o) and save (ctrl+s) files.
  • Cut (ctrl+x), copy (ctrl+c), paste (ctrl+u/v), optionally using the system clipboard. On terminals that support the Kitty keyboard protocol, the cmd equivalents work too.
  • Delete a word with ctrl+backspace, alt+backspace, or alt+delete.
  • Comment selections with ctrl+/.
  • Indent and dedent (optionally for a multiline selection) to tab stops with Tab and shift+Tab.
  • Automatic completions of quotes and brackets.
  • Select text by double-, triple-, or quadruple-clicking.
  • Quit with ctrl+q.

Usage

Initializing the Widget

The TextArea is a Textual Widget. You can add it to a Textual app using compose or mount:

from textual_textarea import TextEditor
from textual.app import App, ComposeResult

class TextApp(App, inherit_bindings=False):
    def compose(self) -> ComposeResult:
        yield TextEditor(text="hi", language="python", theme="nord-darker", id="ta")

    def on_mount(self) -> None:
        editor = self.query_one("#id", expect_type=TextEditor)
        editor.focus()

app = TextApp()
app.run()

In addition to the standard Widget arguments, TextArea accepts several additional, optional arguments when initializing the widget:

  • language (str): Must be None or the short name of a Pygments lexer, e.g., python, sql, as3. Defaults to None.
  • theme (str): Must be name of a Pygments style, e.g., bw, github-dark, solarized-light. Defaults to monokai.
  • use_system_clipboard (bool): Set to False to make the TextArea's copy and paste operations ignore the system clipboard. Defaults to True. Some Linux users may need to apt-install xclip or xsel to enable the system clipboard features.
  • read_only (bool): Set to True to prevent the contents from being edited from the keyboard. Defaults to False.
  • show_cursor (bool): Set to False to hide the cursor (and the shading of the line that contains it), and to scroll the contents like an ordinary container. Defaults to True.

Read-only previews

To show text the user can read, scroll, and copy from, but not edit, pass both read_only=True and show_cursor=False:

yield TextEditor(text=query, language="sql", read_only=True, show_cursor=False, id="preview")

A read_only editor also disables the bindings that open its footer inputs -- save (ctrl+s), open (ctrl+o), find (ctrl+f, F3), and go to line (ctrl+g) -- and lets escape bubble, so the screen showing the preview can bind it to dismiss the preview. To keep some of those bindings, subclass the editor and narrow READ_ONLY_DISABLED_ACTIONS:

class SearchablePreview(TextEditor):
    READ_ONLY_DISABLED_ACTIONS = frozenset({"save", "load"})

The TextArea supports many actions and key bindings. For proper binding of ctrl+c to the COPY action, you must initialize your App with inherit_bindings=False (as shown above), so that ctrl+c does not quit the app. The TextArea implements ctrl+q as quit; you way wish to mimic that in your app so that other in-focus widgets use the same behavior.

Interacting with the Widget

Getting and Setting Text

The TextArea exposes a text property that contains the full text contained in the widget. You can retrieve or set the text by interacting with this property:

editor = self.query_one(TextEditor)
old_text = editor.text
editor.text = "New Text!\n\nMany Lines!"

Similarly, the TextEditor exposes a selected_text property (read-only):

editor = self.query_one(TextEditor)
selection = editor.selected_text

Inserting Text

You can insert text at the current selection:

editor = self.query_one(TextEditor)
editor.text = "01234"
editor.selection = Selection((0, 2), (0, 2))
editor.insert_text_at_selection("\nabc\n")
assert editor.text == "01\nabc\n234"
assert editor.selection == Selection((2, 0), (2, 0))

Getting and Setting The Cursor Position

The TextEditor exposes a selection property that returns a textual.widgets.text_area.Selection:

editor = self.query_one(TextEditor)
old_selection = editor.selection
editor.selection = Selection((999, 0),(999, 0))  # the cursor will move as close to line 999, pos 0 as possible
cursor_line_number = editor.selection.end[0]
cursor_x_position = editor.selection.end[1]

Getting and Setting The Language

Syntax highlighting and comment insertion depends on the configured language for the TextEditor.

The TextArea exposes a language property that returns None or a string that is equal to the short name of an installed tree-sitter language:

editor = self.query_one(TextEditor)
old_language = editor.language
editor.language = "python"

Getting Theme Colors

If you would like the rest of your app to match the colors from the TextArea's theme, they are exposed via the theme_colors property.

editor = self.query_one(TextEditor)
color = editor.theme_colors.contrast_text_color
bgcolor = editor.theme_colors.bgcolor
highlight = editor.theme_colors.selection_bgcolor

Adding Bindings and other Behavior

You can subclass TextEditor to add your own behavior. This snippet adds an action that posts a Submitted message containing the text of the TextEditor when the user presses ctrl+j:

from textual.message import Message
from textual_textarea import TextEditor


class CodeEditor(TextEditor):
    BINDINGS = [
        ("ctrl+j", "submit", "Run Query"),
    ]

    class Submitted(Message, bubble=True):
        def __init__(self, text: str) -> None:
            super().__init__()
            self.text = text

    async def action_submit(self) -> None:
        self.post_message(self.Submitted(self.text))

Metadata

Release files for textual-textarea 0.18.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for textual-textarea 0.18.5
File Size Uploaded
textual_textarea-0.18.5.tar.gz 30.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for textual-textarea 0.18.5
File Interpreter ABI Platform
textual_textarea-0.18.5-py3-none-any.whl Python 3 none any Details

Total release size: 59.0 kB

Release files / textual_textarea-0.18.5.tar.gz

Download URL textual_textarea-0.18.5.tar.gz
Size 30.6 kB
Tags Source
SHA-256 checksum
How to use checksums
21b5c700aee4cdd15c639e09edd737a62da026a138931c1f921c0805a6438967
BLAKE2b-256 checksum
How to use checksums
f635abb09e482584e1ee69de2fc9caf9936e448d1386367bcc4540b7279fe4f1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

Release files / textual_textarea-0.18.5-py3-none-any.whl

Download URL textual_textarea-0.18.5-py3-none-any.whl
Size 28.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2816df18bcf9325dadc8ba321e572bb6d89fd01540c55dbf09923e9252acb18e
BLAKE2b-256 checksum
How to use checksums
47b5bf49b326845c9b1d29e813f2b5ba6c3768b8dea0dc604c6a2758b1802be7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.18.5 This release

2 release files

0.18.4

2 release files

0.18.3

2 release files

0.17.2

2 release files

0.17.1

2 release files

0.17.0

2 release files

0.16.0

2 release files

0.15.0

2 release files

0.14.2

2 release files

0.14.1

2 release files

0.13.1

2 release files

0.13.0

2 release files

0.12.0

2 release files

0.10.0

2 release files

0.9.5

2 release files

0.9.4

2 release files

0.9.3

2 release files

0.9.2

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.3

2 release files

0.7.2

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.4

2 release files

0.5.3

2 release files

0.5.2

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page