> For the complete documentation index, see [llms.txt](https://gyrinx.gitbook.io/gyrinx/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://gyrinx.gitbook.io/gyrinx/explanation/frontend-development.md).

# 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**

```django

<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**

```django

<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**

```django

<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:

```html
<!-- 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**

```html
<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**

```html
<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**

```html
<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

```
n23/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

```bash
# Build the stylesheets for both editions
npm run css

# Watch for changes and rebuild
npm run watch

# Lint SCSS
npm run css-lint
```

`npm run css` builds one stylesheet per edition, and `npm run watch` rebuilds both when a template or stylesheet changes:

| Edition | Source                                       | Output                                         |
| ------- | -------------------------------------------- | ---------------------------------------------- |
| n23     | `n23/core/static/core/scss/` (Sass)          | `n23/core/static/core/css/styles.css`          |
| n26     | `n26/designsystem/assets/app.css` (Tailwind) | `n26/designsystem/static/designsystem/app.css` |

Both outputs are generated: gitignored, built into the Docker image, and never committed. `./scripts/dev.sh` builds them on startup, so you only need to run `npm run css` by hand if you are not using it.

n26's build scans the edition's templates for the classes it emits, which is why editing a template — not just a stylesheet — changes its output. The directories it scans are named explicitly at the top of `app.css`; automatic detection is switched off, because it reaches the whole repository and pulls n23's Bootstrap class names into n26's stylesheet.

### Custom Styling Approach

* Override Bootstrap variables rather than writing custom CSS
* Use Bootstrap utility classes when possible
* Create component-specific styles only when needed

```scss
// _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

```javascript
// 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

```python
# 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'}),
        }
```

```django
<!-- 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

```django
<!-- 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

```bash
# 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

```
n23/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

```html
<!-- 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

```python
@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:

```html
<!-- 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


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://gyrinx.gitbook.io/gyrinx/explanation/frontend-development.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
