Complete technical documentation for the package filtering system architecture, including AlpineJS integration, data flow, and maintenance guidelines.
This document provides technical documentation for the package filtering system used on the “our-packages” page.
Overview
The package listing page uses a client-side filtering system built with AlpineJS to provide instant search and category filtering without page reloads.
Data flow
- Hugo reads
packages.ymlat build time - Package data is injected into AlpineJS via
initPackages({{ .Site.Data.packages | jsonify }}) - AlpineJS manages filtering state and reactive rendering
- JavaScript file handles filter logic
- HTML templates display filtered results
File structure
├── data/packages.yml # Package data (YAML)
├── layouts/packages/list.html # Main page template
├── layouts/partials/packages/grid.html # Grid with AlpineJS card templates
├── layouts/partials/packages/filters.html # Filter buttons and search input
├── layouts/partners/single.html # Static partner pages (no filtering)
└── static/js/package-filter.js # AlpineJS filter component logic
AlpineJS integration
The page uses these key AlpineJS directives:
x-data="packageFilter()"- Creates reactive component instancex-init="initPackages(...)"- Loads package data from Hugo into Alpinex-for="pkg in activePackages"- Loops through filtered packagesx-text,x-html,:href- Binds package data to DOMx-model="searchQuery"- Two-way binding for search input@click="activeFilter = '*'"- Updates filter state
Filtering logic (package-filter.js)
Component state
packages: []- All packages loaded from HugosearchQuery: ''- Current search textactiveFilter: '*'- Current category filter (’*’ = show all)
Computed properties
activePackages- Returns filtered active packagesarchivedPackages- Returns all archived packages (never filtered)
Filter methods
The filterPackages(activeOnly) method:
- Filters by active/archived status
- Filters by category if
activeFilter !== '*' - Filters by search query (name, description, maintainer)
- Returns filtered array
Grid rendering
Structure
<div class="packages-grid"> <!-- CSS Grid container -->
<template x-for="pkg in ..."> <!-- AlpineJS loop -->
<div class="package_card"> <!-- Card markup (70+ lines) -->
<!-- Uses AlpineJS directives (x-text, x-html, :href) -->
<!-- Conditionally shows links based on package data -->
</div>
</template>
</div>
Key Point: Card HTML is INSIDE the AlpineJS template, not a separate partial, because Hugo partials cannot be called from client-side JavaScript.
Card markup architecture
Package cards exist in two places due to the nature of AlpineJS:
A) layouts/partials/packages/grid.html (AlpineJS version)
- Uses
x-text,x-html,:hreffor data binding - Renders client-side
- Used on filterable pages (main packages list)
B) layouts/partners/single.html (Hugo version)
- Uses
{{ .field }}Hugo template syntax - Renders server-side at build time
- Used on static partner pages (no filtering needed)
Why the duplication?
- Hugo partials are processed at BUILD time (server-side)
- AlpineJS templates execute at RUN time (client-side)
- Cannot call Hugo partials from inside
<template x-for> - Trade-off: Accept duplication for simplicity vs. complex Web Components
⚠️ Important: If you update card markup, remember to update BOTH locations!
Styling (_packages.scss)
Key classes
.packages-page__controls- Filter/search container.packages-page__partner-nav- Partner button section.packages-filters- Filter buttons styling.packages-grid- CSS Grid layout (3/2/1 columns).package_card- Individual card styling.package_card__meta- Maintainer info.package_card__archived- Archived badge styling.partner-badge- Astropy badge image styling.partner-button- Partner navigation button
Adding new packages
To add a new package:
- Add entry to
data/packages.ymlwith all required fields - Set
active: trueorfalse - Add
categories(must match filter buttons) - Optionally add
partners: ["astropy"]for partner affiliation - Hugo will rebuild and AlpineJS will automatically include it
Example package entry
- package_name: MyPackage
package_description: A description of the package
submitting_author:
name: Jane Developer
github_username: janedev
all_current_maintainers:
- name: Jane Developer
github_username: janedev
repository_link: https://github.com/example/mypackage
categories:
- data-processing-munging
- geospatial
partners:
- astropy
active: true
gh_meta:
name: MyPackage
description: Package description
stargazers_count: 42
documentation: https://mypackage.readthedocs.io
Adding new filter categories
To add a new category filter:
- Add button to
layouts/partials/packages/filters.html - Use format:
@click="activeFilter = 'category-name'" - Ensure packages in
packages.ymluse matching category name - No JavaScript changes needed - filter logic is generic
Example filter button
<button @click="activeFilter = 'machine-learning'"
:class="{ 'is-checked': activeFilter === 'machine-learning' }">
machine learning
</button>
Performance considerations
- All packages loaded on page load (~340 items = ~500KB JSON)
- Filtering happens in browser (instant, no network requests)
- Good for < 1000 packages
- For larger datasets, consider:
- Pagination
- Server-side filtering
- Lazy loading
- Search indexes
Debugging tips
Browser console commands
Alpine.version // Check AlpineJS is loaded
$el.__x.$data // Inspect Alpine component state
packages // View loaded package data
activeFilter // See current filter
searchQuery // See current search
Common issues
| Issue | Solution |
|---|---|
| “packageFilter is not defined” | package-filter.js not loaded |
| Cards not filtering | Check x-data on parent div |
| Search not working | Check x-model on input |
| Cards not rendering | Check packages data in console |
Future enhancements
Potential improvements:
- URL Query Params -
?category=geospatial&search=data - Sort Options - By stars, recent updates, alphabetical
- Maintainer Filter - Filter by specific maintainer
- Last Updated Filter - Show recently updated packages
- Export Results - Download filtered list as JSON/CSV
- Bookmarking - Share filtered views via URL
- Multi-select Categories - Select multiple categories at once
- Advanced Search - Boolean operators, field-specific search
- Package Comparison - Compare multiple packages side-by-side
Architecture decision records
Why AlpineJS?
- Lightweight (~15KB gzipped)
- Declarative syntax similar to Vue.js
- No build step required
- Works well with Hugo’s server-side rendering
- Easy to learn for theme users
Why client-side filtering?
- Instant filtering with no page reloads
- Better user experience
- Reduced server load
- Package count is manageable (<1000)
- No backend infrastructure needed
Why duplicate card markup?
Alternative Considered: Web Components
Decision: Accept duplication
Rationale:
- Web Components add significant complexity
- Requires JavaScript class definitions, Shadow DOM
- Higher learning curve for theme users
- Harder to debug and maintain
- Two copies is acceptable for ~70 lines of markup
- Card structure is relatively stable
Why YAML for package data?
- Human-readable and editable
- Supports structured data (nested objects, arrays)
- No database required
- Version controlled with site code
- Hugo has excellent YAML support
Maintenance guidelines
When updating card markup
- Update
layouts/partials/packages/grid.html(AlpineJS version) - Update
layouts/partners/single.html(Hugo version) - Test both the main packages page AND partner pages
- Verify filtering still works
- Check responsive layout on mobile
When adding new fields to packages.yml
- Update all existing packages with new field (or make it optional)
- Update card markup in both locations
- Update this documentation
- Consider if field should be searchable/filterable
Testing checklist
- All categories filter correctly
- Search works for name, description, maintainer
- Partner badges display on correct packages
- Archived packages show separately
- Responsive design works on mobile
- No console errors
- Metrics bar calculates correctly
- Links open in correct target
Related documentation
- Theme Color Reference Guide - Theme colors and customization
- AlpineJS Documentation - Official AlpineJS docs
- Hugo Data Files - Hugo data templates
