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))

Release files for textual-textarea 0.18.4

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.4
File Size Uploaded
textual_textarea-0.18.4.tar.gz 30.4 kB Details

Built distribution (wheel)

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

Total release size: 58.8 kB

Release files / textual_textarea-0.18.4.tar.gz

Download URL textual_textarea-0.18.4.tar.gz
Size 30.4 kB
Tags Source
SHA-256 checksum
How to use checksums
d95f96ef88ea8856c5644717498c11decbeeab4b6d02e8124fc6bd4c511c7481
BLAKE2b-256 checksum
How to use checksums
a46d35b6546171af47d134338f5c25eed86ec17ede907d54d09ef8f28824509b
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 Sep 16, 2026.

Transparency log

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

Download URL textual_textarea-0.18.4-py3-none-any.whl
Size 28.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
223209f9008850ea065dfe87bad3369d3bfa9a458ec603a2bb6a90aeec4aa094
BLAKE2b-256 checksum
How to use checksums
b120cc80dc4e097e921bc2565127e0e068401b2a9a03038a055f7dca6f2a7305
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 Sep 16, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.18.4 This release

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