# Building a Tactics Board in Glide

**URL:** <https://community.glideapps.com/t/building-a-tactics-board-in-glide/86990>\
**Category:** General\
**Created:** [May 1, 2026, 12:27pm UTC](https://community.glideapps.com/t/building-a-tactics-board-in-glide/86990 "2026-05-01T12:27:43Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![Simon\_Hill](https://sea2.discourse-cdn.com/flex002/user_avatar/community.glideapps.com/simon_hill/32/94730_2.png) [@Simon\_Hill](https://community.glideapps.com/u/Simon_Hill)\
**Post date:** [May 1, 2026, 12:27pm UTC](https://community.glideapps.com/t/building-a-tactics-board-in-glide/86990/1 "2026-05-01T12:27:43Z")

</div>

## Building a Tactics Board in Glide (and the unexpected workaround)

try the demo link!

I wanted to share a quick showcase of something I’ve been working on in my Football Manager app — a fully interactive tactics board.

This actually started from a different place. There was a discussion in the community around using an AI component to manage JSON and write back state after user interaction. That got me thinking:

“Could I use an AI component to power a drag-and-drop tactics board?”

### Attempt 1: AI Component

I explored the idea of:

- Generating the formation as JSON
- Letting users interact (drag players, change positions)
- Then writing the updated state back via AI

In theory, it sounded ideal. In practice, it got complicated quickly.

The main challenge was managing real-time interaction alongside reliably writing back structured JSON.

After a few iterations, it became clear that this wasn’t the right tool for this specific job.

* * *

### The Pivot (this is where it got interesting)

Instead of forcing it through AI, I switched approach:

Use Glide to construct a URL that passes JSON into an external HTML page.

That HTML page handles:

- The full UI (pitch, players, drag and drop)
- All interaction logic
- Visual feedback

Then Glide simply:

- Builds the URL
- Opens it via a button

* * *

### What’s passed via the URL?

Everything is encoded into the URL as JSON:

- Player names
- Shirt numbers
- Positions (X/Y coordinates)
- Shirt colours (home/away)
- Fixture ID
- Starting formation
- Last saved formation

So Glide becomes the state manager, and the HTML page becomes the interaction layer.

* * *

### Saving the formation

This was the key part to make it actually useful:

- The external page has a Save button
- That triggers a Glide webhook
- The webhook:
  - Adds a new row in a “Formations” table
  - Stores the full updated JSON payload

So now:

- Every time someone opens the tactics board
- They receive a URL that includes:
  - the starting formation
  - the last saved formation

These are then:

- Compared and merged
- Ensuring new players are handled properly

* * *

### Architecture (simple but powerful)

- Glide

- External HTML (hosted page)

* * *

### Result

- Fully interactive tactics board
- Smooth user experience
- Persistent formations
- Works across matches and teams

It is also straightforward to extend further.

* * *

### Side note

This was built over a couple of days (a few hours each day) using some VIBE coding with ChatGPT — around 10 to 11 iterations to get it right.

* * *

### Takeaway

If you’re trying to build something highly interactive in Glide:

Don’t be afraid to step outside Glide for the UI layer and use Glide as the data and orchestration engine.

* * *

If anyone is exploring something similar, especially around JSON and interaction, I’m happy to share more.

here is a link to see it in action:

[https://virtualpatientapp.com/tools/tactics-board-v12.html#players={“starting”%3A{“fixture\_id”%3A"demo12345"%2C"shirt\_color"%3A"%23010101"%2C"players"%3A\[{“team\_sheet\_row\_id”%3A"1"%2C"firstName"%3A"Simon"%2C"lastName"%3A"Hill"%2C"number"%3A"1"%2C"x"%3A50%2C"y"%3A92}%2C{“team\_sheet\_row\_id”%3A"2"%2C"firstName"%3A"Jay"%2C"lastName"%3A"Miller"%2C"number"%3A"2"%2C"x"%3A20%2C"y"%3A78}%2C{“team\_sheet\_row\_id”%3A"3"%2C"firstName"%3A"Bob"%2C"lastName"%3A"Smith"%2C"number"%3A"3"%2C"x"%3A40%2C"y"%3A78}%2C{“team\_sheet\_row\_id”%3A"4"%2C"firstName"%3A"Alex"%2C"lastName"%3A"Turner"%2C"number"%3A"4"%2C"x"%3A60%2C"y"%3A78}%2C{“team\_sheet\_row\_id”%3A"5"%2C"firstName"%3A"Chris"%2C"lastName"%3A"Ford"%2C"number"%3A"5"%2C"x"%3A80%2C"y"%3A78}%2C{“team\_sheet\_row\_id”%3A"6"%2C"firstName"%3A"Dan"%2C"lastName"%3A"Evans"%2C"number"%3A"6"%2C"x"%3A35%2C"y"%3A60}%2C{“team\_sheet\_row\_id”%3A"7"%2C"firstName"%3A"Liam"%2C"lastName"%3A"Jones"%2C"number"%3A"7"%2C"x"%3A65%2C"y"%3A60}%2C{“team\_sheet\_row\_id”%3A"8"%2C"firstName"%3A"Noah"%2C"lastName"%3A"Brown"%2C"number"%3A"8"%2C"x"%3A50%2C"y"%3A50}%2C{“team\_sheet\_row\_id”%3A"9"%2C"firstName"%3A"Owen"%2C"lastName"%3A"Davis"%2C"number"%3A"9"%2C"x"%3A30%2C"y"%3A35}%2C{“team\_sheet\_row\_id”%3A"10"%2C"firstName"%3A"Ethan"%2C"lastName"%3A"Wilson"%2C"number"%3A"10"%2C"x"%3A70%2C"y"%3A35}%2C{“team\_sheet\_row\_id”%3A"11"%2C"firstName"%3A"Leo"%2C"lastName"%3A"Taylor"%2C"number"%3A"11"%2C"x"%3A50%2C"y"%3A20}\\](https://virtualpatientapp.com/tools/tactics-board-v12.html#players=%7B%E2%80%9Cstarting%E2%80%9D%3A%7B%E2%80%9Cfixture_id%E2%80%9D%3A%22demo12345%22%2C%22shirt_color%22%3A%22%23010101%22%2C%22players%22%3A%5C%5B%7B%E2%80%9Cteam_sheet_row_id%E2%80%9D%3A%221%22%2C%22firstName%22%3A%22Simon%22%2C%22lastName%22%3A%22Hill%22%2C%22number%22%3A%221%22%2C%22x%22%3A50%2C%22y%22%3A92%7D%2C%7B%E2%80%9Cteam_sheet_row_id%E2%80%9D%3A%222%22%2C%22firstName%22%3A%22Jay%22%2C%22lastName%22%3A%22Miller%22%2C%22number%22%3A%222%22%2C%22x%22%3A20%2C%22y%22%3A78%7D%2C%7B%E2%80%9Cteam_sheet_row_id%E2%80%9D%3A%223%22%2C%22firstName%22%3A%22Bob%22%2C%22lastName%22%3A%22Smith%22%2C%22number%22%3A%223%22%2C%22x%22%3A40%2C%22y%22%3A78%7D%2C%7B%E2%80%9Cteam_sheet_row_id%E2%80%9D%3A%224%22%2C%22firstName%22%3A%22Alex%22%2C%22lastName%22%3A%22Turner%22%2C%22number%22%3A%224%22%2C%22x%22%3A60%2C%22y%22%3A78%7D%2C%7B%E2%80%9Cteam_sheet_row_id%E2%80%9D%3A%225%22%2C%22firstName%22%3A%22Chris%22%2C%22lastName%22%3A%22Ford%22%2C%22number%22%3A%225%22%2C%22x%22%3A80%2C%22y%22%3A78%7D%2C%7B%E2%80%9Cteam_sheet_row_id%E2%80%9D%3A%226%22%2C%22firstName%22%3A%22Dan%22%2C%22lastName%22%3A%22Evans%22%2C%22number%22%3A%226%22%2C%22x%22%3A35%2C%22y%22%3A60%7D%2C%7B%E2%80%9Cteam_sheet_row_id%E2%80%9D%3A%227%22%2C%22firstName%22%3A%22Liam%22%2C%22lastName%22%3A%22Jones%22%2C%22number%22%3A%227%22%2C%22x%22%3A65%2C%22y%22%3A60%7D%2C%7B%E2%80%9Cteam_sheet_row_id%E2%80%9D%3A%228%22%2C%22firstName%22%3A%22Noah%22%2C%22lastName%22%3A%22Brown%22%2C%22number%22%3A%228%22%2C%22x%22%3A50%2C%22y%22%3A50%7D%2C%7B%E2%80%9Cteam_sheet_row_id%E2%80%9D%3A%229%22%2C%22firstName%22%3A%22Owen%22%2C%22lastName%22%3A%22Davis%22%2C%22number%22%3A%229%22%2C%22x%22%3A30%2C%22y%22%3A35%7D%2C%7B%E2%80%9Cteam_sheet_row_id%E2%80%9D%3A%2210%22%2C%22firstName%22%3A%22Ethan%22%2C%22lastName%22%3A%22Wilson%22%2C%22number%22%3A%2210%22%2C%22x%22%3A70%2C%22y%22%3A35%7D%2C%7B%E2%80%9Cteam_sheet_row_id%E2%80%9D%3A%2211%22%2C%22firstName%22%3A%22Leo%22%2C%22lastName%22%3A%22Taylor%22%2C%22number%22%3A%2211%22%2C%22x%22%3A50%2C%22y%22%3A20%7D%5C%5C) ]}%2C"saved"%3Anull}

 ![IMG_6293](https://us1.discourse-cdn.com/flex002/uploads/glideapps/original/3X/a/c/acf8b5cb9eee553b4f5fe4118f13bde951e61787.jpeg)

 ![IMG_6295](https://us1.discourse-cdn.com/flex002/uploads/glideapps/original/3X/9/0/90ef59023f5b644677fa92cf7f278579c5cb99f4.jpeg)

 ![IMG_6294](https://us1.discourse-cdn.com/flex002/uploads/glideapps/original/3X/f/5/f58357ead78cab767e818c9f08c1374a4927cd32.png)

 ![IMG_6292](https://us1.discourse-cdn.com/flex002/uploads/glideapps/original/3X/f/4/f4b5fa9441db47d7f2314969d83402dcfe8bb469.jpeg)
