Programador Leigo
Flask 9 min leitura 09 MAR 2026

Templates HTML: criando páginas dinâmicas com Python

Separe HTML do Python usando templates Jinja2 com herança, blocos e filtros.


Por que separar HTML do Python

Quando começamos a estudar Flask, é tentador retornar HTML diretamente nas rotas usando strings. Funciona, mas rapidamente vira um pesadelo: código difícil de ler, impossível de manter e sem nenhum reaproveitamento.

A solução é usar templates. O Flask já vem integrado com o Jinja2, um motor de templates poderoso que permite separar toda a lógica de apresentação do restante da aplicação. Assim, o Python cuida das regras de negócio e o HTML fica em arquivos próprios, dentro da pasta templates/.

render_template() básico

O Flask procura os templates dentro de uma pasta chamada templates/ na raiz do projeto. Para renderizar um arquivo, usamos a função render_template().

from flask import Flask, render_template

app = Flask(__name__)

@app.route("/")
def index():
    return render_template("index.html")

Crie o arquivo templates/index.html:

<!DOCTYPE html>
<html lang="pt-br">
<head>
    <meta charset="UTF-8">
    <title>Meu Site</title>
</head>
<body>
    <h1>Bem-vindo ao meu site!</h1>
</body>
</html>

Pronto. O Flask encontra o arquivo, o Jinja2 processa o conteúdo e devolve o HTML final para o navegador.

Passando variáveis para o template

A mágica começa quando enviamos dados do Python para o HTML. Basta passar argumentos nomeados no render_template() e acessá-los no template com a sintaxe {{ variavel }}.

@app.route("/perfil")
def perfil():
    return render_template(
        "perfil.html",
        nome="Maria",
        linguagem="Python",
        nivel=3,
    )

No template templates/perfil.html:

<h1>Perfil de {{ nome }}</h1>
<p>Linguagem favorita: {{ linguagem }}</p>
<p>Nivel de experiencia: {{ nivel }}</p>

Qualquer expressão Python válida funciona dentro das chaves duplas: {{ nome.upper() }}, {{ lista[0] }}, {{ preco * 1.1 }}.

Estruturas de controle no template

O Jinja2 oferece blocos lógicos com a sintaxe {% %}. Os dois mais usados são o if e o for.

@app.route("/dashboard")
def dashboard():
    tarefas = [
        {"titulo": "Estudar Flask", "feita": True},
        {"titulo": "Criar templates", "feita": False},
        {"titulo": "Configurar banco", "feita": False},
    ]
    return render_template("dashboard.html", tarefas=tarefas, usuario="Carlos")

No template templates/dashboard.html:

<h1>Painel de {{ usuario }}</h1>

{% if tarefas %}
    <ul>
    {% for tarefa in tarefas %}
        <li>
            {{ tarefa.titulo }}
            {% if tarefa.feita %}
                <span style="color: green;">-- concluida</span>
            {% else %}
                <span style="color: red;">-- pendente</span>
            {% endif %}
        </li>
    {% endfor %}
    </ul>
{% else %}
    <p>Nenhuma tarefa cadastrada.</p>
{% endif %}

Dentro do for, o Jinja2 disponibiliza a variável especial loop com informações úteis como loop.index (posição começando em 1), loop.first e loop.last.

Herança de templates com {% block %}

Esse é o recurso mais importante do Jinja2. Em vez de repetir cabeçalho, navbar e rodapé em cada página, você cria um template base e as páginas filhas herdam dele, sobrescrevendo apenas os blocos necessários.

Crie o arquivo templates/base.html:

<!DOCTYPE html>
<html lang="pt-br">
<head>
    <meta charset="UTF-8">
    <title>{% block titulo %}Meu Site{% endblock %}</title>
</head>
<body>
    <nav>
        <a href="/">Inicio</a>
        <a href="/sobre">Sobre</a>
        <a href="/contato">Contato</a>
    </nav>

    <main>
        {% block conteudo %}{% endblock %}
    </main>

    <footer>
        <p>Feito com Flask e Jinja2 - 2026</p>
    </footer>
</body>
</html>

Agora, qualquer página pode estender esse layout. Crie templates/sobre.html:

{% extends "base.html" %}

{% block titulo %}Sobre - Meu Site{% endblock %}

{% block conteudo %}
    <h1>Sobre o projeto</h1>
    <p>Este site foi construido com Flask e templates Jinja2.</p>
{% endblock %}

O {% extends %} precisa ser a primeira tag do arquivo. Tudo que estiver dentro dos blocos substitui o conteúdo padrão do template base.

Filtros úteis do Jinja2

Filtros transformam o valor de uma variável usando o operador | (pipe). O Jinja2 traz dezenas de filtros embutidos. Aqui estão os mais práticos:

<!-- Converte para maiusculas -->
<p>{{ nome|upper }}</p>

<!-- Conta a quantidade de itens -->
<p>Total de tarefas: {{ tarefas|length }}</p>

<!-- Define valor padrao caso a variavel nao exista -->
<p>Cor favorita: {{ cor|default("nao informada") }}</p>

<!-- Renderiza HTML sem escapar (cuidado com XSS!) -->
<div>{{ conteudo_html|safe }}</div>

<!-- Trunca texto longo -->
<p>{{ descricao|truncate(100) }}</p>

<!-- Capitaliza a primeira letra -->
<p>{{ cidade|capitalize }}</p>

Na rota, basta enviar as variáveis normalmente:

@app.route("/filtros")
def filtros():
    return render_template(
        "filtros.html",
        nome="maria",
        tarefas=["item1", "item2", "item3"],
        conteudo_html="<strong>Texto em negrito</strong>",
        descricao="Um texto muito longo que precisa ser cortado para caber no card",
        cidade="sao paulo",
    )

Includes com {% include %}

O {% include %} permite inserir o conteúdo de um template dentro de outro. É útil para componentes reutilizáveis que não precisam de herança completa.

Crie templates/componentes/alerta.html:

<div class="alerta">
    <p>{{ mensagem_alerta }}</p>
</div>

Use em qualquer página:

{% extends "base.html" %}

{% block conteudo %}
    <h1>Pagina inicial</h1>

    {% include "componentes/alerta.html" %}

    <p>Restante do conteudo aqui.</p>
{% endblock %}

O template incluído tem acesso a todas as variáveis do contexto atual. Você também pode silenciar erros caso o arquivo não exista:

{% include "componentes/banner.html" ignore missing %}

Macros: funções dentro do template

Macros funcionam como funções reutilizáveis dentro dos templates. São perfeitas para componentes que recebem parâmetros.

Crie templates/macros/formulario.html:

{% macro campo_texto(nome, rotulo, tipo="text") %}
    <div class="campo">
        <label for="{{ nome }}">{{ rotulo }}</label>
        <input type="{{ tipo }}" id="{{ nome }}" name="{{ nome }}">
    </div>
{% endmacro %}

Para usar a macro em outra página, importe-a primeiro:

{% from "macros/formulario.html" import campo_texto %}

{% extends "base.html" %}

{% block conteudo %}
    <form method="POST">
        {{ campo_texto("nome", "Seu nome") }}
        {{ campo_texto("email", "Seu e-mail", tipo="email") }}
        {{ campo_texto("senha", "Sua senha", tipo="password") }}
        <button type="submit">Enviar</button>
    </form>
{% endblock %}

Isso evita repetição e mantém os formulários consistentes em todo o projeto.

Exemplo prático: layout completo com navbar e footer

Vamos juntar tudo em um exemplo realista. Primeiro, a estrutura de pastas:

projeto/
  app.py
  templates/
    base.html
    index.html
    componentes/
      navbar.html
      footer.html

Arquivo templates/componentes/navbar.html:

<nav style="background: #333; padding: 1rem;">
    <a href="/" style="color: white;">Inicio</a>
    <a href="/artigos" style="color: white;">Artigos</a>
    <a href="/contato" style="color: white;">Contato</a>

    {% if usuario_logado %}
        <span style="color: #0f0;">Ola, {{ usuario_logado }}!</span>
    {% else %}
        <a href="/login" style="color: #ff0;">Entrar</a>
    {% endif %}
</nav>

Arquivo templates/componentes/footer.html:

<footer style="background: #333; color: white; padding: 1rem; margin-top: 2rem;">
    <p>Todos os direitos reservados - {{ ano_atual }}</p>
    <p>Feito com Flask e Jinja2</p>
</footer>

Arquivo templates/base.html (versão final):

<!DOCTYPE html>
<html lang="pt-br">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block titulo %}Meu Site Flask{% endblock %}</title>
    {% block head_extra %}{% endblock %}
</head>
<body>
    {% include "componentes/navbar.html" %}

    <main style="padding: 2rem;">
        {% block conteudo %}{% endblock %}
    </main>

    {% include "componentes/footer.html" %}
</body>
</html>

Arquivo templates/index.html:

{% extends "base.html" %}

{% block titulo %}Pagina Inicial{% endblock %}

{% block conteudo %}
    <h1>Bem-vindo, {{ nome|default("visitante") }}!</h1>

    {% if artigos %}
        <h2>Ultimos artigos</h2>
        <ul>
        {% for artigo in artigos %}
            <li>{{ loop.index }}. {{ artigo.titulo|capitalize }}</li>
        {% endfor %}
        </ul>
    {% else %}
        <p>Nenhum artigo publicado ainda.</p>
    {% endif %}
{% endblock %}

E o app.py que conecta tudo:

from datetime import datetime
from flask import Flask, render_template

app = Flask(__name__)

@app.route("/")
def index():
    artigos = [
        {"titulo": "primeiros passos com flask"},
        {"titulo": "templates com jinja2"},
        {"titulo": "rotas e metodos HTTP"},
    ]
    return render_template(
        "index.html",
        nome="Maria",
        artigos=artigos,
        usuario_logado="Maria",
        ano_atual=datetime.now().year,
    )

if __name__ == "__main__":
    app.run(debug=True)

Recapitulando

O sistema de templates do Flask com Jinja2 oferece tudo que você precisa para construir interfaces organizadas e reutilizáveis. Os conceitos principais são:

  • render_template() para renderizar arquivos HTML
  • {{ }} para exibir variáveis e expressões
  • {% %} para lógica como condicionais e loops
  • extends/block para herança de layout
  • include para inserir componentes
  • macros para criar funções reutilizáveis no template
  • filtros para transformar dados na exibição

No próximo artigo da série, vamos trabalhar com formulários e métodos HTTP no Flask, conectando o front-end ao back-end de verdade.

Continue lendo

Compartilhar