Native Tkinter Entry widgets don't have support for placeholders—text that appears when the field is empty and that disappears when it's not.

In this blog post we'll attempt to add this functionality to a custom Tkinter widget, and show you how we'd do it!

The idea for this blog post came from one of our student's questions in our course, GUI Development with Python and Tkinter. If you'd like to learn much more about Tkinter and GUI development, check out the course!

What's in a placeholder?

The best way to start implementing a placeholder in Tkinter is to think about the way a placeholder behaves, in terms of the user's interaction. When we do that, we will be able to translate this behaviour into events that we can bind to the widget.

Usually Tkinter placeholders are implemented in this way:

  • Placeholder text appears when the field loses focus, if the user hasn't typed anything
  • Placeholder text disappears when the field gains focus (but before the user types)

HTML placeholders behave slightly differently but their behaviour is more difficult to replicate with Tkinter, so we will stick with this implementation!

Creating a custom Entry widget

To create a custom widget, what we do is code a class and make it inherit from the target widget. Then in that class we can make changes to the widget as necessary.

Let's start off by inheriting from ttk.Entry in a new class:

from tkinter import ttk


class PlaceholderEntry(ttk.Entry):
    def __init__(self, container, *args, **kwargs):
        super().__init__(container, *args, **kwargs)

What that code means is that whenever you create a PlaceholderEntry object, it is the same as creating a ttk.Entry object. So now let's extend it so that the field starts off with some text. We will:

  • Add a required placeholder parameter to the __init__ method.
  • Insert the text in the widget.

Let's look at how to do that:

from tkinter import ttk


class PlaceholderEntry(ttk.Entry):
    def __init__(self, container, placeholder, *args, **kwargs):
        super().__init__(container, *args, **kwargs)
        
        self.insert("1.0", placeholder)

Next up, we will make it so that when the text gains focus, the contents are deleted.

Adding an event binding

To delete the contents when the text gains focus, we will add an event binding to the field. Functions can be bound on specific events such as <KeyPressed>, <FocusIn>, or <FocusOut>. We will make use of the two latter ones for this implementation:

from tkinter import ttk


class PlaceholderEntry(ttk.Entry):
    def __init__(self, container, placeholder, *args, **kwargs):
        super().__init__(container, *args, **kwargs)

		self.insert("0", placeholder)
		self.bind("<FocusIn>", self._clear_placeholder)

	def _clear_placeholder(self, e):
		self.delete("0", "end")

Next up, let's make it so that when the field loses focus the placeholder is added back (as long as there is no text written in the field already).

from tkinter import ttk


class PlaceholderEntry(ttk.Entry):
    def __init__(self, container, placeholder, *args, **kwargs):
        super().__init__(container, *args, **kwargs)
		self.placeholder = placeholder

		self.insert("0", self.placeholder)
		self.bind("<FocusIn>", self._clear_placeholder)
		self.bind("<FocusOut>", self._add_placeholder)

	def _clear_placeholder(self, e):
		self.delete("0", "end")

	def _add_placeholder(self, e):
		if not self.get():
            self.insert("0", self.placeholder)

At the moment we have a couple bugs:

  • If the user types something in the field and then clicks away, giving focus back to the field will delete everything they've typed.
  • The placeholder looks like text that has been typed into the field, so it might be a bit confusing!

Let's fix both of these in one go.

Changing the text colour of the placeholder

Tkinter fields have a background colour (the colour of the text box) and a foreground colour (the colour of the text). In order to change the placeholder colour, we must change the foreground colour every time the placeholder appears; and change it back when the placeholder disappears so that the user's text is in its normal colour.

To do this we will define a style for the entry field with a placeholder (with a light-grey colour foreground). We will set this style as the default for this entry class.

Whenever we clear the placeholder we will reset back the style to the default, "TEntry". Here we can also make it so that we won't delete the user's content whenever the field loses focus as well:

import tkinter as tk
from tkinter import ttk


class PlaceholderEntry(ttk.Entry):
    def __init__(self, container, placeholder, *args, **kwargs):
        super().__init__(container, *args, style="Placeholder.TEntry", **kwargs)
		self.placeholder = placeholder

		self.insert("0", self.placeholder)
		self.bind("<FocusIn>", self._clear_placeholder)
		self.bind("<FocusOut>", self._add_placeholder)

	def _clear_placeholder(self, e):
		if self["style"] == "Placeholder.TEntry":
			self.delete("0", "end")
			self["style"] = "TEntry"

	def _add_placeholder(self, e):
		if not self.get():
			self.insert("0", self.placeholder)
			self["style"] = "Placeholder.TEntry"


root = tk.Tk()
style = ttk.Style(root)

style.configure("Placeholder.TEntry", foreground="#d5d5d5")

entry = PlaceholderEntry(root, "Sample placeholder")
entry.pack()

root.mainloop()

Allowing for a custom placeholder style

Users of your custom entry field class may not want to use the "Placeholder.TEntry" style every time. They may want to name it something different, so they can use their own.

When you're creating custom widgets to be used in multiple places, you should avoid using hard-coded styles and settings. Instead what you can do is allow for keyword arguments to be passed. You can also have sensible defaults if you think that's suitable.

Remember that if our field tries to use "Placeholder.TEntry", but the user did not define that earlier on, we will crash with an error.

class PlaceholderEntry(ttk.Entry):
    def __init__(self, container, placeholder, *args, **kwargs):
        super().__init__(container, *args, **kwargs)
		self.placeholder = placeholder

		self.field_style = kwargs.pop("style", "TEntry")
		self.placeholder_style = kwargs.pop("placeholder_style", self.field_style)
		self["style"] = self.placeholder_style

		self.insert("0", self.placeholder)
		self.bind("<FocusIn>", self._clear_placeholder)
		self.bind("<FocusOut>", self._add_placeholder)

	def _clear_placeholder(self, e):
		if self["style"] == self.placeholder_style:
			self.delete("0", "end")
			self["style"] = self.field_style

	def _add_placeholder(self, e):
		if not self.get():
			self.insert("0", self.placeholder)
			self["style"] = self.placeholder_style

With these changes, now we can create PlaceholderEntry widgets like so:

entry = PlaceholderEntry(
    root,
	"Sample Placeholder",
	style="TEntry",
	placeholder_style="Placeholder.TEntry"
)

And if you don't pass a style or placeholder_style, the default "TEntry" will be used for both. If you only pass style, then placeholder_style will take its value too so the field always looks the same.

Wrapping Up

We've learned both how to create a custom widget, and also how to add placeholder text to an entry field. Feel free to take the PlaceholderEntry class and re-use it in your own projects!

I hope this has been useful. If so, and if you want to learn much more about Tkinter and GUI Development with Python, check out our course: GUI Development with Python and Tkinter. We'd love to have you in the course.

Thank you for reading, and I'll see you next time!