AppSavvyBook a call
Canvas (Airdev)

Canvas Reusable Elements: Best Practices and Anti-Patterns

Reusable elements are Canvas's biggest leverage and biggest debt source. The patterns that work, the ones that don't, and how to refactor an app full of bad reusables.

Will Driscoll9 min read

Reusable elements are Canvas's biggest leverage. They're how you avoid copy-pasting the same modal across 30 pages, how you keep design consistency without a designer in every PR, and how you onboard new engineers without making them learn every nook of every page.

They're also Canvas's biggest debt source. A poorly-designed reusable spreads its problems through every page it touches. A well-designed reusable amplifies good decisions across the whole app.

This article walks through the patterns we use at AppSavvy - and the anti-patterns we see in Canvas apps that have grown organically without a reusable-element discipline.

The four rules of good reusables

Before any patterns: there are four rules that, if you follow them, mean most of your reusable design problems disappear.

  1. One reusable, one responsibility. If you can't describe what the reusable does in one sentence, split it.
  2. Inputs are explicit, outputs are explicit. Custom states or a clear data type sent in; events triggered out. No implicit "this reusable reads from the URL" or "this reusable updates the Current User."
  3. No business logic. A reusable can render a list or show a modal. It can't decide whether the user is allowed to do something - that's the parent's job.
  4. No silent side effects. A reusable should never write to a Data Type the parent didn't ask it to.

If your existing reusables broke these rules from day one (most do), that's a refactor candidate.

The patterns that work

Pattern 1: pure presentational reusables

These are the cleanest pattern. The reusable receives data via custom states, renders a UI, triggers events on user actions.

Examples:

  • A user avatar (input: User; output: optional click event)
  • A status badge (input: status string; output: nothing)
  • A repeating group cell template (input: row data; output: row click event)

Pure presentational reusables don't talk to the database. They just render. This makes them composable, testable in isolation, and safe to use anywhere.

Pattern 2: form reusables with explicit submission events

A form reusable that:

  • Accepts initial values as inputs
  • Renders the form UI
  • Validates on the client
  • On submit, triggers an event with the form values

Critically, the form reusable doesn't write to the database itself. The parent listens to the submit event, decides what to do with the values, and performs the write.

This sounds like more work than letting the reusable do the write, but it pays off when:

  • You want to use the same form in two contexts (create new vs. edit existing)
  • You want to inject custom validation in some places
  • You want different post-submit behaviour (close modal vs. redirect)

Pattern 3: modal/drawer reusables with a clear lifecycle

Modal reusables that:

  • Have an explicit "open" event the parent can fire
  • Manage their own visibility (don't make the parent toggle a state)
  • Trigger a "closed" event when dismissed
  • Trigger a "completed" event when the action succeeds

These compose well with form reusables - the modal hosts the form, the form triggers submit, the modal closes on successful submit.

Pattern 4: list reusables with parameterised filtering

A list reusable that:

  • Receives a search criteria object as input (the parent computes what to show)
  • Renders the list with that criteria
  • Triggers row-click events

This pattern works well when you have several pages showing variations of the same list (admin vs. user view of orders, for example) but the rendering is identical.

The anti-patterns to fix

Anti-pattern 1: the "magic" reusable

The reusable that reads from the URL, reads from the Current User, reads from arbitrary URL parameters, and renders something based on the combination. Nobody can predict its behaviour without reading the source.

Fix: pass everything in as explicit inputs. Even if it makes the parent verbose.

Anti-pattern 2: the reusable that talks to the database

A reusable that performs its own searches, updates records, makes API calls. When the parent uses it, the database side-effects happen invisibly.

Fix: move the database operations into the parent's workflows. The reusable only renders and triggers events.

Anti-pattern 3: the "options" reusable with 30 input parameters

A reusable that's so configurable it requires 30 custom states to use. By the time you've configured it for your use case, you've written more state setup than just inlining the UI would have taken.

Fix: split into multiple smaller reusables, or accept that some duplication is healthier than over-abstraction.

Anti-pattern 4: the reusable used exactly once

A reusable that's referenced in exactly one place. The "reusable" name is aspirational; in practice it's just extracted UI.

Fix: either inline it back into the page (if the abstraction isn't earning its keep) or commit to making it actually reusable (use it in at least one more place).

Anti-pattern 5: nested reusables 4+ levels deep

Reusable A contains reusable B contains reusable C contains reusable D. The chain of custom state propagation makes any change require touching every level.

Fix: flatten. Most cases can be handled with at most 2 levels of nesting.

Anti-pattern 6: the reusable with its own privacy rules

A reusable that depends on privacy rules being set up a particular way on the parent page. Subtle, hard to debug.

Fix: make access decisions in the parent, not the reusable. The reusable just renders what the parent says to render.

How to refactor a reusable-element mess

If you've inherited an app that has many of these anti-patterns, here's the order we usually approach refactoring:

Step 1: inventory

List every reusable element. For each:

  • Where is it used?
  • What inputs does it take?
  • Does it write to the database?
  • Does it read from outside its inputs (URL, Current User, arbitrary searches)?

This is part of the tech debt audit but worth doing in detail before any refactor.

Step 2: pick the highest-impact one

Refactor doesn't mean "fix everything." Pick the reusable that:

  • Is used in the most places, AND
  • Has the most anti-pattern issues

Fixing that one reusable improves the most pages at once.

Step 3: design the new interface before changing anything

For the chosen reusable, sketch out (on paper, or in a doc):

  • The new input signature
  • The new output events
  • What goes back into the parent

Get it reviewed by another engineer or, if you're solo, sleep on it. The interface is harder to change than the implementation later.

Step 4: implement the new version alongside the old

Create the new reusable with a different name (UserAvatar-v2 or similar). The old one keeps working; nothing breaks.

Step 5: migrate usage page by page

Replace the old reusable with the new one one page at a time. Test each page. Don't try to do a global find-and-replace.

Step 6: archive the old reusable

When no page uses the old one, remove it (or rename it to make removal obvious in the next clean-up).

Step 7: repeat

Pick the next highest-impact reusable. Repeat.

This is slow. A team refactoring 8-10 critical reusables this way takes 6-12 weeks. The alternative - a big rewrite of everything at once - takes longer and breaks more.

When refactoring isn't worth it

Sometimes the right answer is: don't refactor the reusables, the whole app is heading to a code migration and the time would be better invested there. If your app is one to two years from migration anyway, weigh the refactor work against just preparing for the migration.

For apps with a longer Canvas life ahead, the refactor pays back fast.

What to do next

If you'd like an external review of your reusable element inventory and refactor priorities, request a free Bubble app audit or book a 30-minute discovery call.

Read next: The Canvas tech debt audit and Working with the Canvas style system.

Got a Bubble or Canvas app you’d like a second pair of eyes on?

30-minute discovery call. We’ll look at your app live and tell you honestly what we’d do next.

Or grab the Bubble migration playbook PDF.