Skip to content

Interactive Periodic Table

Abstract

The periodic table is a beautiful data-visualization problem: 118 elements, each with a dozen properties, arranged in a grid that itself encodes meaning (rows = periods, columns = groups). In this tutorial you build a Tkinter app where each element is a clickable button that pops up its details. Then you scale it from 10 elements to all 118, lay them out in the real periodic-table shape, color them by category (metals, nonmetals, noble gases), and add live search and filtering.

You will leave understanding:

  • How to generate a grid of widgets from a data list with grid(row=, column=)grid(row=, column=).
  • The classic late-binding lambda bug in loops — and the e=elemente=element fix.
  • How to drive layout from data (atomic number → grid position).
  • How to color-code and filter a dataset in a GUI.

Prerequisites

  • Python 3.6 or above.
  • A text editor or IDE.
  • Tkinter (bundled with Python).
  • Comfort with lists, dictionaries, and loops.

Getting Started

Create the project

  1. Create a folder named periodic-tableperiodic-table.
  2. Inside it, create interactive_periodic_table.pyinteractive_periodic_table.py.

Write the code

interactive_periodic_table.pySource
interactive_periodic_table.py
"""
Interactive Periodic Table
 
A Python application that displays an interactive periodic table. Features include:
- Displaying information about elements when clicked.
- Searching for elements by name or symbol.
"""
 
import tkinter as tk
from tkinter import messagebox
 
# Sample data for the periodic table
elements = [
    {"symbol": "H", "name": "Hydrogen", "atomic_number": 1, "atomic_mass": 1.008},
    {"symbol": "He", "name": "Helium", "atomic_number": 2, "atomic_mass": 4.0026},
    {"symbol": "Li", "name": "Lithium", "atomic_number": 3, "atomic_mass": 6.94},
    {"symbol": "Be", "name": "Beryllium", "atomic_number": 4, "atomic_mass": 9.0122},
    {"symbol": "B", "name": "Boron", "atomic_number": 5, "atomic_mass": 10.81},
    {"symbol": "C", "name": "Carbon", "atomic_number": 6, "atomic_mass": 12.011},
    {"symbol": "N", "name": "Nitrogen", "atomic_number": 7, "atomic_mass": 14.007},
    {"symbol": "O", "name": "Oxygen", "atomic_number": 8, "atomic_mass": 15.999},
    {"symbol": "F", "name": "Fluorine", "atomic_number": 9, "atomic_mass": 18.998},
    {"symbol": "Ne", "name": "Neon", "atomic_number": 10, "atomic_mass": 20.180},
]
 
 
class InteractivePeriodicTable:
    def __init__(self, root):
        self.root = root
        self.root.title("Interactive Periodic Table")
 
        self.create_table()
 
    def create_table(self):
        """Create the periodic table layout."""
        for i, element in enumerate(elements):
            button = tk.Button(
                self.root,
                text=element["symbol"],
                width=10,
                height=3,
                command=lambda e=element: self.show_element_info(e),
            )
            button.grid(row=i // 5, column=i % 5, padx=5, pady=5)
 
    def show_element_info(self, element):
        """Display information about the selected element."""
        info = (
            f"Name: {element['name']}\n"
            f"Symbol: {element['symbol']}\n"
            f"Atomic Number: {element['atomic_number']}\n"
            f"Atomic Mass: {element['atomic_mass']}"
        )
        messagebox.showinfo("Element Information", info)
 
 
def main():
    root = tk.Tk()
    app = InteractivePeriodicTable(root)
    root.mainloop()
 
 
if __name__ == "__main__":
    main()
 
interactive_periodic_table.py
"""
Interactive Periodic Table
 
A Python application that displays an interactive periodic table. Features include:
- Displaying information about elements when clicked.
- Searching for elements by name or symbol.
"""
 
import tkinter as tk
from tkinter import messagebox
 
# Sample data for the periodic table
elements = [
    {"symbol": "H", "name": "Hydrogen", "atomic_number": 1, "atomic_mass": 1.008},
    {"symbol": "He", "name": "Helium", "atomic_number": 2, "atomic_mass": 4.0026},
    {"symbol": "Li", "name": "Lithium", "atomic_number": 3, "atomic_mass": 6.94},
    {"symbol": "Be", "name": "Beryllium", "atomic_number": 4, "atomic_mass": 9.0122},
    {"symbol": "B", "name": "Boron", "atomic_number": 5, "atomic_mass": 10.81},
    {"symbol": "C", "name": "Carbon", "atomic_number": 6, "atomic_mass": 12.011},
    {"symbol": "N", "name": "Nitrogen", "atomic_number": 7, "atomic_mass": 14.007},
    {"symbol": "O", "name": "Oxygen", "atomic_number": 8, "atomic_mass": 15.999},
    {"symbol": "F", "name": "Fluorine", "atomic_number": 9, "atomic_mass": 18.998},
    {"symbol": "Ne", "name": "Neon", "atomic_number": 10, "atomic_mass": 20.180},
]
 
 
class InteractivePeriodicTable:
    def __init__(self, root):
        self.root = root
        self.root.title("Interactive Periodic Table")
 
        self.create_table()
 
    def create_table(self):
        """Create the periodic table layout."""
        for i, element in enumerate(elements):
            button = tk.Button(
                self.root,
                text=element["symbol"],
                width=10,
                height=3,
                command=lambda e=element: self.show_element_info(e),
            )
            button.grid(row=i // 5, column=i % 5, padx=5, pady=5)
 
    def show_element_info(self, element):
        """Display information about the selected element."""
        info = (
            f"Name: {element['name']}\n"
            f"Symbol: {element['symbol']}\n"
            f"Atomic Number: {element['atomic_number']}\n"
            f"Atomic Mass: {element['atomic_mass']}"
        )
        messagebox.showinfo("Element Information", info)
 
 
def main():
    root = tk.Tk()
    app = InteractivePeriodicTable(root)
    root.mainloop()
 
 
if __name__ == "__main__":
    main()
 

Run it

command
C:\Users\Your Name\periodic-table> python interactive_periodic_table.py
# A grid of element buttons appears. Click one to see its details.
command
C:\Users\Your Name\periodic-table> python interactive_periodic_table.py
# A grid of element buttons appears. Click one to see its details.

Step-by-Step Explanation

1. Elements as data

interactive_periodic_table.py
elements = [
    {"symbol": "H",  "name": "Hydrogen", "atomic_number": 1, "atomic_mass": 1.008},
    {"symbol": "He", "name": "Helium",   "atomic_number": 2, "atomic_mass": 4.0026},
    ...
]
interactive_periodic_table.py
elements = [
    {"symbol": "H",  "name": "Hydrogen", "atomic_number": 1, "atomic_mass": 1.008},
    {"symbol": "He", "name": "Helium",   "atomic_number": 2, "atomic_mass": 4.0026},
    ...
]

Each element is a dict. The UI is generated from this list — add an element here and a button appears automatically. This data-drives-the-view idea is the heart of the project.

2. Building the grid

interactive_periodic_table.py
for i, element in enumerate(elements):
    button = tk.Button(
        self.root, text=element["symbol"], width=10, height=3,
        command=lambda e=element: self.show_element_info(e),
    )
    button.grid(row=i // 5, column=i % 5, padx=5, pady=5)
interactive_periodic_table.py
for i, element in enumerate(elements):
    button = tk.Button(
        self.root, text=element["symbol"], width=10, height=3,
        command=lambda e=element: self.show_element_info(e),
    )
    button.grid(row=i // 5, column=i % 5, padx=5, pady=5)

gridgrid places widgets in rows and columns. The integer math is the trick: i // 5i // 5 is the row, i % 5i % 5 is the column — so elements flow left-to-right, wrapping every 5. Change the 55 to change the columns per row.

3. The lambda gotcha (critical)

interactive_periodic_table.py
command=lambda e=element: self.show_element_info(e)
interactive_periodic_table.py
command=lambda e=element: self.show_element_info(e)

Why e=elemente=element and not just lambda: self.show_element_info(element)lambda: self.show_element_info(element)? Because of late binding: a bare lambda captures the variable elementelement, not its current value. By the time you click, the loop has finished and elementelement points at the last item — so every button would show Neon. Binding e=elemente=element as a default argument snapshots the value now. This is the single most common Tkinter-loop bug.

4. Showing details

interactive_periodic_table.py
def show_element_info(self, element):
    info = (
        f"Name: {element['name']}\n"
        f"Symbol: {element['symbol']}\n"
        f"Atomic Number: {element['atomic_number']}\n"
        f"Atomic Mass: {element['atomic_mass']}"
    )
    messagebox.showinfo("Element Information", info)
interactive_periodic_table.py
def show_element_info(self, element):
    info = (
        f"Name: {element['name']}\n"
        f"Symbol: {element['symbol']}\n"
        f"Atomic Number: {element['atomic_number']}\n"
        f"Atomic Mass: {element['atomic_mass']}"
    )
    messagebox.showinfo("Element Information", info)

A popup is fine to start; a side panel that updates in place feels far more like a real app (below).

Scale to All 118 Elements — in the Real Layout

The actual periodic table isn’t a 5-wide wrap; it’s an 18-column grid with gaps. Store each element’s groupgroup (column) and periodperiod (row) and place it precisely:

layout.py
# Each element gains "period" and "group" fields
for el in elements:
    tk.Button(self.root, text=el["symbol"], width=4, height=2,
              command=lambda e=el: self.show(e)).grid(
        row=el["period"], column=el["group"], padx=1, pady=1)
layout.py
# Each element gains "period" and "group" fields
for el in elements:
    tk.Button(self.root, text=el["symbol"], width=4, height=2,
              command=lambda e=el: self.show(e)).grid(
        row=el["period"], column=el["group"], padx=1, pady=1)

The lanthanides and actinides sit in two detached rows below — exactly as on a wall chart. Pull a full dataset from a JSON file rather than typing 118 dicts by hand.

Color by Category

Chemistry uses color to group elements. Map categories to colors:

colors.py
CATEGORY_COLORS = {
    "alkali metal": "#ff6666", "noble gas": "#66ccff",
    "nonmetal": "#99ff99", "metalloid": "#ffcc66", "transition metal": "#cccccc",
}
btn.config(bg=CATEGORY_COLORS.get(el["category"], "white"))
colors.py
CATEGORY_COLORS = {
    "alkali metal": "#ff6666", "noble gas": "#66ccff",
    "nonmetal": "#99ff99", "metalloid": "#ffcc66", "transition metal": "#cccccc",
}
btn.config(bg=CATEGORY_COLORS.get(el["category"], "white"))

Suddenly the table teaches — trends jump out visually.

Add Search and Filtering

An Entry that highlights matches as you type:

search.py
def on_search(self, query):
    q = query.lower()
    for el, btn in self.buttons.items():
        match = q in el["name"].lower() or q == el["symbol"].lower()
        btn.config(relief="solid" if match and q else "raised")
search.py
def on_search(self, query):
    q = query.lower()
    for el, btn in self.buttons.items():
        match = q in el["name"].lower() or q == el["symbol"].lower()
        btn.config(relief="solid" if match and q else "raised")

Filter by category with a dropdown to dim everything except, say, the noble gases.

Upgrade the Detail View

Replace the popup with a persistent side panel showing electron configuration, melting/boiling points, discovery year, and a short description. Update it on click instead of spawning dialogs — it feels like a reference tool, not a quiz.

Common Mistakes

ProblemCauseFix
Every button shows the last elementLate-binding lambdaBind the value: lambda e=element: ...lambda e=element: ...
Elements pile in one columnWrong rowrow/columncolumn mathUse i // colsi // cols and i % colsi % cols, or real group/period
Layout looks nothing like the chartUsed a simple wrapPlace by groupgroup/periodperiod with gaps
Window huge or buttons tinyFixed sizes vs. 118 cellsSmaller widthwidth/heightheight; let the grid expand
Typing 118 dicts by hand is error-proneHard-coded dataLoad elements from a JSON dataset
Search does nothingCompared against symbol case-sensitivelyNormalize with .lower().lower()

Variations to Try

  1. Full dataset — all 118 elements with 15+ properties from JSON.
  2. Trend heatmaps — color by electronegativity or atomic radius.
  3. Element quiz — “click the noble gases” timed challenge.
  4. Compare mode — select two elements and diff their properties.
  5. 3D Bohr model — draw electron shells for the selected element.
  6. Unit toggles — switch mass/temperature units.
  7. Export — save the selected element as a flashcard image.

Real-World Applications

  • Education — chemistry teaching and revision tools.
  • Reference apps — quick element lookup for students and engineers.
  • Data-viz practice — encoding multiple dimensions (color, position) at once.
  • Material selection — filtering elements by property ranges.

Educational Value

  • Data-driven UI — generating widgets from a dataset.
  • Grid layout math — mapping an index (or group/period) to position.
  • Closures & late binding — the most-asked-about Python loop bug.
  • Visual encoding — color and position carrying meaning.

Next Steps

  • Load all 118 elements from a JSON dataset.
  • Lay them out in the real group/period grid.
  • Color by category and add search + filtering.
  • Swap the popup for a live detail panel.

Conclusion

You built an interactive periodic table, dodged the infamous late-binding lambda trap, and learned to drive a UI entirely from data. Scaled to 118 color-coded, searchable elements in their true layout, it becomes a genuine reference and teaching tool — and the data-drives-the-view pattern you practiced powers dashboards, seating charts, and any grid-of-things UI you’ll build next. Full source on GitHub. Explore more visualization projects on Python Central Hub.

If this helped you, consider buying me a coffee ☕

Buy me a coffee

Was this page helpful?

Let us know how we did