Tkinter: como criar interfaces gráficas em Python (guia completo)

Aprenda a criar interfaces gráficas com Tkinter em Python: primeira janela, layouts com pack e grid, widgets, eventos, um mini-app prático, CustomTkinter para visual moderno e como transformar tudo em EXE.

28 Sep 2026 7 min de leitura Equipe Python Dev BR

Para criar uma interface gráfica em Python, use o Tkinter — que já vem instalado com o Python: crie a janela com tk.Tk(), adicione widgets como Label, Entry e Button, organize com pack() ou grid() e rode mainloop(). Nenhuma instalação extra é necessária, o que faz do Tkinter o caminho mais rápido para transformar um script de linha de comando em um programinha de janela que qualquer colega consegue usar clicando.

Quem pergunta a um assistente “como criar um programa com janela em Python?” ou “como fazer minha automação ter botões?” precisa de uma página que vá além do hello world: como posicionar widgets de verdade com grid, capturar o que o usuário digitou, reagir a eventos e chegar a um app funcional que dá para entregar — inclusive transformado em EXE com PyInstaller. É isso que este guia cobre, na ordem em que um iniciante em Python precisa.

Por que Tkinter em 2026?

O Tkinter é o binding oficial do Python para o Tk, e é a interface gráfica da biblioteca padrão desde sempre. Isso rende três vantagens concretas:

  • Zero setup: import tkinter funciona em qualquer Python instalado do python.org no Windows e no macOS.
  • Documentação enorme: tutoriais, respostas no Stack Overflow e exemplos em português desde os anos 2000.
  • Leveza: abre instantâneo e roda em qualquer máquina de escritório — diferente de frameworks que puxam centenas de megabytes.

O visual padrão lembra os anos 2000, e é justo aí que entra o CustomTkinter, mais abaixo. O conhecimento de Tkinter não é desperdiçado: CustomTkinter usa os mesmos conceitos de widget, layout e eventos.

A tabela de decisão rápida:

FerramentaVem com o Python?VisualMelhor para
TkinterSimDatado (melhora com ttk)Utilitários, automações, aprendizado
CustomTkinterNão (pip install)Moderno, dark modeMesmo público do Tkinter, com cara atual
PySide6 (Qt)NãoProfissionalApps comerciais grandes e complexos
Flet / KivyNãoModerno, mobileApps que também rodam em celular
TextualNãoTerminal (TUI)Painéis para quem vive no terminal

Para ferramentas de linha de comando ou painéis no terminal, o caminho é outro: veja CLIs com Python e TUIs com Textual.

Sua primeira janela em 6 linhas

Crie um arquivo app.py:

import tkinter as tk

janela = tk.Tk()
janela.title("Meu primeiro app")
janela.geometry("360x180")

rotulo = tk.Label(janela, text="Olá, Python!")
rotulo.pack(padx=20, pady=20)

botao = tk.Button(janela, text="Fechar", command=janela.destroy)
botao.pack(pady=10)

janela.mainloop()

Rode com python app.py. Se aparecer a janela, seu ambiente já tem Tkinter funcionando. Cada linha importa:

  • tk.Tk() cria a janela principal — só uma por aplicação.
  • title() e geometry() configuram título e tamanho (largura x altura).
  • Widgets (Label, Button) recebem a janela como parent.
  • pack() posiciona o widget na tela.
  • mainloop() é o loop de eventos: ele mantém a janela viva e reagindo a cliques até ser fechada. Tudo que você escrever depois dele só roda quando a janela fechar.

Se import tkinter falhar no Linux, o pacote veio separado: sudo apt install python3-tk resolve (a instalação no Linux tem os detalhes por distribuição).

Layout: pack, grid e place

O erro nº 1 do iniciante é brigar com posicionamento. A regra: escolha grid para formulários e janelas reais; pack para empilhar barras simples.

  • pack() empilha widgets lado a lado ou de cima a baixo (side="top", "left"…). Bom para barras de botões.
  • grid() divide a janela em linhas e colunas invisíveis — uma planilha. Cada widget ocupa uma célula com row e column.
  • place() posiciona por coordenadas absolutas. Evite: janelas redimensionáveis quebram.

Nunca misture pack e grid no mesmo parent — o Tkinter entra em loop infinito esperando um layout decidir o outro.

Exemplo com grid — o esqueleto de qualquer formulário:

import tkinter as tk
from tkinter import ttk

janela = tk.Tk()
janela.title("Cadastro")
ttk.Label(janela, text="Nome:").grid(row=0, column=0, sticky="e", padx=8, pady=8)
ttk.Entry(janela, width=30).grid(row=0, column=1, padx=8)
ttk.Label(janela, text="E-mail:").grid(row=1, column=0, sticky="e", padx=8, pady=8)
ttk.Entry(janela, width=30).grid(row=1, column=1, padx=8)
ttk.Button(janela, text="Salvar").grid(row=2, column=1, sticky="e", padx=8, pady=8)

janela.mainloop()

Truques de grid que valem decorar:

  • sticky="e" alinha o widget à direita da célula (leste) — rótulos ficam colados aos campos.
  • columnconfigure(1, weight=1) faz a coluna 1 esticar quando a janela é redimensionada.
  • padx/pady adicionam respiro — interface sem espaçamento parece bug, não minimalista.
  • Prefira os widgets ttk (ttk.Label, ttk.Button): são os mesmos widgets com visual nativo do sistema.

Os widgets essenciais (e o que cada um captura)

WidgetPara quêComo ler o valor
ttk.LabelTexto e imagens estáticas—
ttk.EntryCampo de uma linhaentry.get()
tk.TextTexto multilinhatext.get("1.0", "end")
ttk.ComboboxLista de opções editávelcombo.get()
ttk.CheckbuttonSim/nãovia tk.BooleanVar
ttk.ScaleDeslizante numéricovia tk.DoubleVar
ttk.TreeviewTabelas com linhas e colunasiteração sobre get_children()
ttk.NotebookAbasnotebook.select()
ttk.ProgressbarBarra de progressobarra["value"] = 50

Variáveis de controle (tk.StringVar, BooleanVar, DoubleVar) conectam widget e lógica: mudou a variável, o widget atualiza sozinho. É a forma idiomática de ler e escrever valores:

nome = tk.StringVar()
campo = ttk.Entry(janela, textvariable=nome)
# ...
print(nome.get())          # lê o que foi digitado
nome.set("Maria")          # preenche o campo programaticamente

Eventos: reagindo ao usuário

Há três formas de ligar código a ações, da mais à menos comum:

1. command em botões — a mais simples. Passe uma função (sem parênteses!):

def salvar():
    print(f"Salvando {nome.get()}")

ttk.Button(janela, text="Salvar", command=salvar)

2. bind para eventos de janela/teclado — quando você precisa de teclas, mouse ou redimensionamento:

janela.bind("<Return>", lambda evento: salvar())   # Enter salva
janela.bind("<Escape>", lambda evento: janela.destroy())

3. trace em variáveis — reagir em tempo real a mudanças de valor:

def ao_mudar(*_):
    rotulo.config(text=f"Olá, {nome.get() or 'visitante'}!")

nome.trace_add("write", ao_mudar)

Uma pegadinha clássica: se o app congela durante uma tarefa longa (processar uma planilha, baixar um arquivo), é porque o mainloop roda na mesma thread da sua função. Para tarefas demoradas, use threading — o guia de Threading e ThreadPoolExecutor mostra o padrão completo com Tkinter.

Mini-app completo: conversor de temperaturas

Juntando tudo — grid, StringVar, command e validação — em um app de verdade. Trinta linhas, sem dependências:

import tkinter as tk
from tkinter import ttk, messagebox


class ConversorApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Conversor de temperatura")
        self.geometry("320x140")
        self.resizable(False, False)

        self.celsius = tk.StringVar()

        ttk.Label(self, text="Celsius:").grid(row=0, column=0, sticky="e", padx=8, pady=8)
        ttk.Entry(self, width=12, textvariable=self.celsius).grid(row=0, column=1, padx=8)
        ttk.Button(self, text="Converter", command=self.converter).grid(row=0, column=2, padx=8)

        self.resultado = ttk.Label(self, text="—", font=("", 12, "bold"))
        self.resultado.grid(row=1, column=0, columnspan=3, pady=12)

        self.bind("<Return>", lambda e: self.converter())

    def converter(self):
        try:
            valor = float(self.celsius.get().replace(",", "."))
        except ValueError:
            messagebox.showerror("Valor inválido", "Digite um número, ex.: 25.5")
            return
        fahrenheit = valor * 9 / 5 + 32
        self.resultado.config(text=f"{valor:.1f} °C = {fahrenheit:.1f} °F")


if __name__ == "__main__":
    ConversorApp().mainloop()

Repare nos detalhes que separam um script de um app: replace(",", ".") aceita vírgula decimal (padrão brasileiro), messagebox valida antes de calcular, Enter também dispara a conversão e a classe herda de tk.Tk para organizar estado e widgets no mesmo lugar. Troque a fórmula por qualquer cálculo do seu trabalho — conversão de planilhas ou validação de CPF, CNPJ e CEP — e o esqueleto continua o mesmo.

CustomTkinter: visual moderno sem reaprender

Se o objetivo é um app bonito sem sair do ecossistema Tk, o CustomTkinter troca os widgets padrão por versões arredondadas, com dark mode e temas de cor:

pip install customtkinter
import customtkinter as ctk

ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("blue")

app = ctk.CTk()
app.title("Meu app moderno")
app.geometry("360x200")

ctk.CTkLabel(app, text="Olá, Python moderno!", font=("", 16)).pack(pady=24)
ctk.CTkButton(app, text="Fechar", command=app.destroy).pack(pady=12)

app.mainloop()

A migração é quase mecânica: tk.Tk → ctk.CTk, ttk.Label → ctk.CTkLabel, e os layouts (grid, pack) continuam idênticos. Para utilitários internos com cara de produto, é o melhor custo-benefício em Python puro.

Entregando: de janela a EXE

O usuário final não vai rodar python app.py. O caminho é empacotar com PyInstaller em um ambiente virtual limpo:

pyinstaller --onefile --noconsole --name conversor app.py
  • --noconsole impede que uma janela de terminal abra junto (essencial para apps gráficos no Windows).
  • --windowed é sinônimo; ambos só fazem sentido em apps com GUI.
  • O resultado em dist\conversor.exe roda em qualquer Windows sem Python instalado.

O guia completo de PyInstaller aqui do site cobre ícone, arquivo .spec, hiddenimports e o falso positivo de antivírus — leia antes de distribuir.

Checklist antes de mostrar o app para alguém

  1. Layout com grid, espaçamento (padx/pady) e janela com tamanho inicial sensato.
  2. Validação de entrada com messagebox — nunca deixe um traceback estourar na cara do usuário.
  3. Valores numéricos aceitam vírgula decimal (replace(",", ".")).
  4. Tarefas demoradas em threading, para a janela não congelar.
  5. Widgets ttk (ou CustomTkinter) em vez dos clássicos datados.
  6. EXE gerado com --noconsole e testado em máquina sem Python.

Conclusão

O Tkinter continua sendo a porta de entrada mais honesta para GUIs em Python: nada para instalar, documentação de sobra e um modelo de widgets/grid/eventos que se transfere direto para CustomTkinter. Comece com a janela de 6 linhas, evolua para o mini-app de formulário, capriche no visual com CustomTkinter e feche o ciclo distribuindo um EXE. Quando o projeto crescer para muitas telas e tabelas pesadas, aí sim o PySide6 entra — mas você já terá o vocabulário inteiro de interfaces gráficas aprendido.

Leia também

E
Equipe Python Dev BR

Contribuidor do Python Dev BR

Artigos relacionados