I used to finish a design and then make it again eleven times.

Same card, different customer name. Same banner, the client's orange instead of our purple. Same certificate, except this person's name is thirty-four characters long and now nothing fits.

None of that is design. It is typing, with a layout tool open.

What changed it for me was treating a design the way I would treat a spreadsheet. A cell in a spreadsheet is either a value or a formula. A layer in a design can be the same. The headline is not "Summer sale". The headline is whatever the headline is this time, and the colour behind it is a quiet version of whatever the brand colour is this time.

Here are two renders of one template. Between them I changed a brand name, one colour and the text. I did not touch a single layer.

The template rendered for an orange brand, with a summary line

The same template rendered for a green brand, with no summary line

Look at what moved on its own. The highlight behind the last word, the icon tile, the section label and the arrow all followed the one colour. The second version has no summary, so that line is gone and nothing is left holding its place. The breadcrumb went from two items to one.

That template uses four functions. These are the seven kinds of formula I reach for, in the order I usually add them. The syntax is from bannx, the tool I build and use, but the ideas work anywhere a design tool lets you bind a property to an expression.

1. The fallback: no layer is ever empty

{{ use(headline, 'Your headline here') }} 

use returns the first value that exists. If a headline was passed in, you get it. If not, you get the placeholder.

It reads as a small thing. It is the reason a template survives contact with real data. Every variable I declare gets a fallback on day one, and they chain:

{{ use(brand.logos.logo, brand.logos.icon) }} 

Full logo if there is one, the icon if there is not. The person filling in the template never sees a broken image, and I never get the message that starts with "quick question about the logo".

2. The condition: layers that know when to leave

A fallback fills a gap. Sometimes the right answer is for the gap not to exist.

Every layer can carry a condition. Mine are usually one word long:

isDefined(page.description) 

No description, no layer. Because the layout around it stacks automatically, everything below moves up. That is what happened to the summary line in the green render above.

Before this I kept two versions of most templates: "with subtitle" and "without subtitle". Now the template makes that decision per render.

3. One colour in, a whole palette out

This is the one that made me stop picking colours.

{{ alpha(brand.colors.primary, 0.15) }} {{ mix(brand.colors.primary, '#ffffff', 0.5) }} {{ tint(brand.colors.primary, 0.9) }} 

The icon tile is the brand colour at low opacity. The label is the brand colour mixed toward white. A pale panel is a tint. I choose the relationships once. The brand supplies the one real colour, and the rest follows.

When a client's orange arrives, I change one value and the whole design is theirs. I do not go hunting for the eleven places I used a lighter purple.

4. Text that stays readable on a colour you have not seen yet

A button in the brand colour needs a label. White works on navy. White on yellow is unreadable. I will not be there when the yellow brand shows up, so the layer decides:

{{ isDark(brand.colors.primary) ? '#ffffff' : '#000000' }} 

If the background is dark, use white text. Otherwise use black. There is also contrast(a, b), which returns the actual contrast ratio between two colours, for when you want to be strict about the 4.5 that accessibility guidelines ask for.

It is one line, and it removes a whole category of "looks great in the mockup, illegible in production".

5. Lists: draw one row, get all of them

Design one row. Point it at a list. You get a row per item.

That is how the breadcrumb above works, and it is how I build speaker line-ups, price tables and feature lists. The list can also come from a formula. My favourite:

calendarMonthSlots(year, month, 1) 

That returns 42 cells: a full month laid out in weeks starting on Monday, with blanks where the month has not started yet. One designed day cell, two numbers in, any month of any year out. I have not drawn a calendar grid by hand since.

6. Numbers that add themselves up

Invoices, receipts, order summaries. The total should never be typed by a person.

{{ money(sumItems(items, 'amount') * (1 + toNumber(taxRate, 0) / 100), currency) }} 

Read it from the inside out. Add up the amount of every item. Apply the tax rate, or zero if nobody gave one. Format the result as money in the right currency.

The part I like most is that sumItems accepts messy input. "$1,234.50" as a string adds up correctly. Real data arrives looking like that, and the design should cope rather than print NaN on a customer's invoice.

7. Randomness that holds still

Perfectly aligned stickers look machine-made. A few degrees of tilt fixes that.

{{ randomFloat(-3, 3, 'sticker-' + itemIndex) }}deg 

The third argument is a seed. With the same seed you get the same "random" number every time, so the tilt does not reshuffle whenever I touch something else. Each item in a list has its own index, so each gets its own angle. It looks hand-placed, and it renders identically today and next month.

A bonus one, for motion

When the design moves, time is just another variable:

{{ slice(headline, 0, floor(clamp(($time - 0.4) / 1.2, 0, 1) * len(headline))) }} 

That is a typewriter. It shows more of the headline as time passes, starting at 0.4 seconds and finishing 1.2 seconds later. It works on any headline of any length, because the formula asks how long the headline is instead of assuming.

The rules I ended up with

After a lot of templates, a few habits stuck.

Design the worst case first. I set the longest name, the biggest number and the missing logo before I make anything look nice. A layout that survives those survives everything.

Every variable gets a default. A template that renders nothing without input is a template nobody can preview.

One line or it becomes a variable. If a formula needs a comment to explain it, I move the hard part into a named variable. isOnSale is easier to read in six months than the comparison it stands for.

Formulas describe relationships, not values. "40% of the brand colour" will still be right next year. "#E9D8FD" will not.

What it changed

I make fewer files. One template with seven small formulas replaces a folder of near-duplicates, and the duplicates were where the mistakes lived: the old price left in version nine, the logo nobody updated in version four.

The work feels different as well. I spend my time deciding how things relate, which is the part of design I actually enjoy, and none of it retyping names.

If you already do this in your own tool, I would like to know which formula you could not live without. Mine is the first one. It is the least clever and it has saved me the most.