Skip to main content
Themes are written by merchants, and shoppers pay on the page they produce. These rules keep the payment block honest. They are checked when a theme is saved (PATCH /v1/themes/:id, POST /v1/themes/validate) and a refused document is a THEME_INVALID error with a list of problems:
path is dotted: blocks.2.html, blocks.4.columns.0.1.alt, settings.button_label, custom_css. CSS problems start with Line N:. At render time the same checks protect the page again, and a theme that cannot be parsed falls back to the Tendr default look.

What themes can never do

  • Select, cover, move or restyle the sealed payment block.
  • Add any shopper input: no forms, inputs, buttons, selects, textareas or editable text.
  • Load code, other pages, or files from outside Tendr.
  • Show a wallet address, payment instructions or a payment timer of their own.
  • Write words with CSS.
Your CSS only applies inside your own blocks. Each of your blocks is clipped and isolated, so negative margins, transform, large z-index values and similar tricks stay inside the block and cannot reach the payment block.

Custom CSS

Maximum 50 KB. Your rules are scoped to your blocks, so selectors need no prefix.

Selectors

* is fine as part of a longer selector, such as .card *.

At-rules

Only @media, @supports and @keyframes are allowed. Everything else, including @import and @font-face, is refused with `@import` isn't allowed — only `@media`, `@supports` and `@keyframes` are. To use your own font, upload it and choose it in the theme settings. @keyframes needs a plain name and frame selectors that are from, to or percentages. Tendr prefixes your animation names so they cannot replace its own.

Properties and values

CSS cannot draw text

An address can be assembled from short strings spread over several pseudo-elements or keyframes, and no pattern check can catch that reliably. So CSS may not draw any text at all. These properties accept only text-free values, and anything else is refused with CSS can't add text — put words in a text block instead:
  • content: none, normal, "", the quote keywords (open-quote, close-quote, no-open-quote, no-close-quote), one counter(name) and url() of an uploaded image. Not attr(), counters(), var(), env(), string(), symbols() or any non-empty string.
  • quotes: none or auto.
  • list-style, list-style-type, text-emphasis, text-emphasis-style, text-overflow and hyphenate-character: keywords only.
  • var() in any of these properties.
Counters and list markers draw digits or bullets only:
  • counter() and list-style-type accept decimal, decimal-leading-zero, disc, circle, square or none. Anything else: Counters and list markers can only use `decimal`, `decimal-leading-zero`, `disc`, `circle`, `square` or `none`.
  • content can show at most one counter(): `content` can show at most one `counter()`.
  • counter-reset, counter-set and counter-increment take whole numbers from -999 to 999: Counter values must be whole numbers from -999 to 999.

Custom HTML

The custom_html block takes up to 20 KB (HTML block is over 20 KB). Tendr sanitises it on save and stores the cleaned HTML. Allowed tags: p br span div strong em b i u s small h2 h3 h4 ul ol li blockquote a img figure figcaption hr table thead tbody tr th td. Allowed attributes: class and style on any tag, href, rel and target on a, src and alt on img. Other tags are dropped, and these are refused outright: Links get rel="noopener noreferrer" and target="_blank" automatically.

Text rules

These apply to every text a theme carries: headings, rich text, banners, alt text, FAQ, reviews, delivery notes, footer link labels, countdown labels, the display name, the success message and the button label. Address-like text. Refused with Text can't contain wallet addresses or payment instructions:
  • EVM addresses: 0x followed by 40 hex characters.
  • Bitcoin bech32: bc1… or tb1….
  • Base58 strings of 25 to 44 characters that mix letters and digits.
  • 64 hex characters (NEAR implicit accounts).
  • NEAR names ending in .near, .testnet or .tg.
In rich text, link targets (the https://… part of [text](https://…)) are not checked for base58 strings, because they are not shown and URL paths often look like one; the link text is checked like any other text. Invisible and format characters are stripped before checking, and in HTML the checks run on the visible text of the whole block, so splitting an address across tags does not hide it. Payment instructions. A phrase like “send … to the address”, “transfer … to wallet”, “deposit … to account 0x” or “pay … to bc1” is refused with the same message. Countdown labels. A countdown label cannot look like the payment timer: no expire, expires, expiring, expiry, session, deadline, time left, pay within or left to pay. The message is Countdown text can't look like the payment timer (avoid words like expires, session, deadline, time left). Button words. settings.button_label follows these rules: The message names the word that was refused. Everyday words that are also codes, such as Try, all, top or cup, are fine unless written in capitals. {amount} is filled in by Tendr with the real amount. The label applies to the main Pay button only. Rich text. rich_text cannot contain HTML (Text can't contain HTML), inline images (Text can't contain inline images), reference-style links (Reference-style links are not supported), or links that do not start with https:// (Links in text must start with https://). Other document rules.

Risks Tendr accepts

Pattern checks on text are best effort. These cases cannot be fully detected:
  • Address-like text split by hidden elements or punctuation. Pieces of an address separated by invisible spans (display: none, zero font size) or by punctuation can read as an address to a shopper without matching a pattern.
  • An image showing a fake QR code or address. Tendr can validate that a file is an image, not what the picture shows.
  • A video showing a fake QR code or address. Like images, an embedded video can show anything; Tendr does not inspect what it shows.
The sealed payment block is the source of truth, and the defence. It always shows the real address and QR code, in Tendr’s own type, and states that funds go only there. A merchant who dresses a fake address up in their own blocks is attacking their own customers, and the sealed block still tells the shopper the truth.