Skip to content

Last updated: 25th March 2026

Components

Info

Most of these have not been implemented yet and are subject to change.

Philosophy

Every visible UI element that appears in more than one place is a component — a self-contained, reusable template fragment. Nothing is duplicated. If a design change is needed, it happens in one file and propagates everywhere automatically.

This is Django's {% include %} tag put to disciplined use.

templates/
├── base.html               # master layout — orchestrates everything below
├── components/             # structural UI — present on most/all pages
│   ├── _nav.html
│   ├── _footer.html
│   └── _cta.html
└── partials/               # content fragments — often HTMX targets
    ├── _project_card.html
    ├── _project_grid.html
    ├── _blog_card.html
    ├── _contact_form.html
    └── _contact_success.html

components/ — structural chrome that wraps every page. Included statically in base.html. Never an HTMX target.

partials/ — content fragments that can be rendered standalone. These are what HTMX requests return. A GET /projects/?tag=django response is just _project_grid.html, not a full page.


Component Inventory

_nav.html

The navigation bar. Included once in base.html. Contains:

  • Site logo / name (links to core:home)
  • Navigation links (About, Work, Projects, Blog)
  • Language switcher
  • Theme toggle

Keeping nav isolated means restyling or adding a link touches exactly one file.

_footer.html

Site footer. Included once in base.html. Contains:

  • Social links (GitHub, LinkedIn)
  • Copyright line
  • Secondary navigation if needed

_cta.html

The "Contact me" call-to-action block. Appears at the bottom of the landing page and at the end of the About and Work pages. One file, three placements:

{% include "components/_cta.html" %}

If the copy, styling, or destination URL ever changes, it changes once.


Content Partials

_project_card.html

Renders a single Project instance — title, description snippet, tag chips, GitHub/live links. Used by _project_grid.html.

<!-- Expects a `project` variable in context -->
<div class="card">
  <h3>{{ project.title }}</h3>
  <p>{{ project.description }}</p>
  {% for tag in project.tags.all %}
    <span class="badge">{{ tag.name }}</span>
  {% endfor %}
</div>

_project_grid.html

Loops over a projects queryset and renders a _project_card.html for each. This is the HTMX target for tag filtering — the entire grid is swapped in one operation without touching the nav, filters, or page heading.

{% for project in projects %}
  {% include "partials/_project_card.html" %}
{% empty %}
  <p>No projects found.</p>
{% endfor %}

_blog_card.html

The blog post preview — title, published date, opening paragraph or excerpt, and a "Read more" link. The equivalent of _project_card.html for posts. Each entry on the /blog/ listing page is one of these.

_contact_form.html / _contact_success.html

The two states of the contact form interaction. The form renders by default; on successful submission HTMX swaps it for the success confirmation. Both are partials so they can be returned directly from a view without a full page render.


The {% include %} + Context Pattern

Django's {% include %} passes the current template context down by default. For cases where you need to pass specific data, use the with keyword:

<!-- Pass a specific object -->
{% include "partials/_project_card.html" with project=featured_project %}

<!-- Pass data and isolate context (only the passed vars are available) -->
{% include "partials/_project_card.html" with project=p only %}

The only keyword is useful for partials that should be fully self-contained — it prevents accidental dependence on whatever happens to be in the parent context.


Why This Matters Long-Term

Without this discipline, a typical growth path looks like:

Week 1:  Copy-paste the nav into home.html, about.html, work.html
Week 8:  Add a new nav link — edit 6 files
Week 12: Redesign the project card — hunt down every place it's used
Week 16: The footer on the contact page is slightly different and nobody knows why

With components:

Week 1:  Write _nav.html once, include it in base.html
Week 8:  Add a new nav link — edit 1 file
Week 12: Redesign the project card — edit 1 file
Week 16: The footer is identical everywhere because there is only one footer

The upfront cost is naming discipline and one extra {% include %} tag. The long-term payoff is a codebase where changes are local and auditable.