Frontend Development
Gyrinx follows a server-rendered approach using Django templates with Bootstrap 5 for styling. The frontend emphasizes simplicity, accessibility, and mobile-first design.
Technology Stack
Core Technologies
Django Templates - Server-side rendering with template inheritance
Bootstrap 5 - CSS framework for responsive design
SCSS - CSS preprocessing for maintainable styles
Vanilla JavaScript - Minimal client-side interactivity
Build Tools
npm - Package management and build scripts
SCSS compilation - CSS preprocessing
No bundlers - Keep it simple with direct file serving
Template Architecture
Template Hierarchy
templates/
├── core/layouts/
│ ├── base.html # Root template with <html>, <head>, <body>
│ ├── page.html # Simple content pages
│ └── foundation.html # Minimal template for special cases
├── core/includes/
│ ├── back.html # Standard back button component
│ ├── fighter_card.html # Fighter display component
│ └── ... # Other reusable components
└── core/
├── list.html # Page-specific templates
├── campaign.html
└── ...
Template Usage Patterns
Full Page Layout
<div data-gb-custom-block data-tag="extends" data-0='core/layouts/base.html'></div>
<div data-gb-custom-block data-tag="block">My Page Title</div>
<div data-gb-custom-block data-tag="block">
<div class="container">
<h1>Page Content</h1>
</div>
</div>
Simple Content Page
<div data-gb-custom-block data-tag="extends" data-0='core/layouts/page.html'></div>
<div data-gb-custom-block data-tag="block">Simple Page</div>
<div data-gb-custom-block data-tag="block">
<p>Simple content without navigation complexity</p>
</div>
Back Button Navigation
<div data-gb-custom-block data-tag="include" data-0='core/includes/back.html' data-text='Back to Lists'></div>
Styling with Bootstrap 5
Mobile-First Approach
All designs start with mobile layout and scale up:
<!-- Mobile-first column layout -->
<div class="row">
<div class="col-12 col-md-8">
<!-- Main content: full width on mobile, 8/12 on desktop -->
</div>
<div class="col-12 col-md-4">
<!-- Sidebar: full width on mobile, 4/12 on desktop -->
</div>
</div>
Common Bootstrap Patterns
Cards for Content Grouping
<div class="card">
<div class="card-header">
<h5 class="card-title">Section Title</h5>
</div>
<div class="card-body">
<p class="card-text">Content goes here</p>
</div>
</div>
Form Styling
<form method="post">
<div data-gb-custom-block data-tag="csrf_token"></div>
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control" id="name" name="name">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
Responsive Tables
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>Fighter</th>
<th>Cost</th>
<th class="d-none d-md-table-cell">Equipment</th>
</tr>
</thead>
<!-- Table content -->
</table>
</div>
SCSS Development
File Structure
gyrinx/core/static/core/scss/
├── styles.scss # Main entry point
├── _variables.scss # Custom Bootstrap variables
├── _components.scss # Custom component styles
└── _utilities.scss # Utility classes
Build Process
# Compile SCSS to CSS
npm run css
# Watch for changes and rebuild
npm run watch
# Lint SCSS
npm run css-lint
Custom Styling Approach
Override Bootstrap variables rather than writing custom CSS
Use Bootstrap utility classes when possible
Create component-specific styles only when needed
// _variables.scss - Override Bootstrap defaults
$primary: #your-brand-color;
$font-family-base: 'Your-Font', sans-serif;
// _components.scss - Custom components
.fighter-card {
@extend .card;
.fighter-name {
@extend .card-title;
color: $primary;
}
}
JavaScript Usage
Minimal JavaScript Philosophy
Gyrinx uses minimal JavaScript, favoring server-side rendering and simple form submissions.
When to Use JavaScript
Form enhancements (show/hide fields)
Client-side validation feedback
Simple interactive elements (dropdowns, modals)
Progressive enhancement only
JavaScript Patterns
// Progressive enhancement
document.addEventListener('DOMContentLoaded', function() {
// Only enhance if JavaScript is available
const enhancedElements = document.querySelectorAll('.js-enhance');
enhancedElements.forEach(element => {
// Add JavaScript behavior
});
});
// Avoid JavaScript dependencies for core functionality
// Core features must work without JavaScript
Form Handling
Django Form Integration
# forms.py
class ListForm(forms.ModelForm):
class Meta:
model = List
fields = ['name', 'content_house', 'public']
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'content_house': forms.Select(attrs={'class': 'form-select'}),
'public': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
}
<!-- Template -->
<form method="post">
<div data-gb-custom-block data-tag="csrf_token"></div>
{{ form.as_p }}
<button type="submit" class="btn btn-primary">Save</button>
</form>
Custom Form Rendering
<!-- Manual form field rendering for better control -->
<div class="mb-3">
<label for="{{ form.name.id_for_label }}" class="form-label">
{{ form.name.label }}
</label>
{{ form.name }}
<div data-gb-custom-block data-tag="if">
<div class="text-danger">{{ form.name.errors }}</div>
</div>
</div>
Static File Management
Development
# Collect static files for development
manage collectstatic --noinput
# Files are served by Django in development
DEBUG=True # in settings_dev.py
Production
WhiteNoise serves static files
Files are collected during deployment
CSS is compiled from SCSS in build process
File Organization
gyrinx/core/static/core/
├── css/
│ └── styles.css # Compiled from SCSS
├── js/
│ └── index.js # Minimal JavaScript
├── img/
│ ├── brand/ # Logos and branding
│ └── content/ # Game-related images
└── scss/
└── ... # Source SCSS files
Performance Considerations
Template Performance
Use `
` efficiently - Cache template fragments with `` - Minimize database queries in templates
CSS Performance
Minimize custom CSS
Use Bootstrap utilities to reduce file size
Optimize images for web delivery
JavaScript Performance
Load JavaScript at end of
<body>
Use event delegation for dynamic content
Avoid large JavaScript frameworks
Accessibility
Built-in Bootstrap Accessibility
Use semantic HTML elements
Leverage Bootstrap's ARIA attributes
Ensure keyboard navigation works
Custom Accessibility
<!-- Proper labeling -->
<label for="fighter-name">Fighter Name</label>
<input type="text" id="fighter-name" name="name" required>
<!-- ARIA attributes for dynamic content -->
<div role="alert" aria-live="polite" id="status-message"></div>
<!-- Focus management -->
<button class="btn btn-primary" aria-expanded="false" data-bs-toggle="collapse">
Toggle Section
</button>
Testing Frontend Code
Template Testing
@pytest.mark.django_db
def test_list_template_renders():
client = Client()
user = User.objects.create_user(username="test", password="test")
response = client.get("/lists/")
assert response.status_code == 200
assert "Lists" in response.content.decode()
CSS Testing
Visual regression testing (manual)
Cross-browser testing on common devices
Mobile responsiveness testing
JavaScript Testing
Manual testing for progressive enhancement
Ensure core functionality works without JavaScript
Future Considerations
htmx Integration
While not currently used, htmx could add interactivity:
<!-- Potential future pattern -->
<button hx-post="/lists/create/" hx-target="#list-container">
Create List
</button>
Performance Monitoring
Consider adding Core Web Vitals monitoring
Monitor CSS bundle size growth
Track JavaScript execution time
Last updated