Building a Tactics Board in Glide

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

    • Stores player data
    • Builds encoded URL
    • Handles persistence via webhook
  • External HTML (hosted page)

    • Renders the pitch
    • Handles drag and drop
    • Sends updates back

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}\\ ]}%2C"saved"%3Anull}

2 Likes