Grona components
Grona components are the native views the SDK draws inside a slot view: a banner, a progress bar, an offer and a message. This page lists what each component shows and which fields a campaign can set, so you know what Grona can put in your app without a release. Your app only places the slot view; the content comes from the signed configuration.
How components get their content
Section titled “How components get their content”Each SDK-rendered campaign carries a render object with a component field and that component’s fields. The SDK validates every field when it loads the configuration. A campaign with an unknown field, a missing required field or a malformed colour is inactive, and the rest of the configuration still applies.
Every text field is a map from locale to string, for example {"en": "Weekend breakfast", "fr": "Petit-déjeuner du week-end"}. The SDK picks the shopper’s locale from the context your app sets, and falls back to the workspace’s fallback locale (en unless the configuration says otherwise).
- Text is plain text, at most 2,000 characters. No HTML, no links, no markdown.
- The SDK strips control characters other than newline and tab, and the bidirectional embedding and override characters that could reorder digits in right-to-left scripts.
- The SDK fills these templates:
{total},{gap},{reward},{min},{name},{price}and{count}(the number of products). Unknown tokens stay as written. - A known token with no value hides that text. If the text is required (a title or a call-to-action label), the whole component is hidden.
- Money renders from minor units: whole amounts without decimals (
60), otherwise with two (12.99). - Product names and prices always come from your app’s product data, never from the configuration.
Colours
Section titled “Colours”A campaign can set style with background, text and accent, each as #RRGGBB. Any colour the campaign leaves out falls back to your app’s theme tokens. The SDK also corrects text colours that would not be readable on the background. See Theming and accessibility.
Images
Section titled “Images”A banner image is one of:
- An image bundled in your app, as
asset://img/<path>. The path must sit underimg/in your app’s assets. - An
httpsURL on a host listed in the workspace’s image host allowlist.
Anything else is dropped. A dropped or failed image means no image; the component still renders.
Banner
Section titled “Banner”A card with an optional eyebrow, a title, optional body text, an optional image and an optional call-to-action button. Use it for promotions and nudges such as a weekend breakfast banner.
| Field | Required | Meaning |
|---|---|---|
eyebrow |
No | Short label above the title |
title |
Yes | Main line |
body |
No | Supporting text |
image |
No | Bundled asset or allowlisted https image, shown beside the text |
cta |
No | {label, action}: the button label and what a tap does |
style |
No | background, text, accent |
fold_on_action |
No | When true, the banner collapses after its action succeeds. Default false |
The call to action runs only from the shopper’s tap. It does one of three things:
| Action | Shape |
|---|---|
| Add several products | {"type": "add_all", "products": [{"id": "sku_20877"}, {"id": "sku_31005", "qty": 2}]} |
| Add one product | {"type": "add", "product": {"id": "sku_10432"}} |
| Navigate | {"type": "navigate", "route": {"type": "category", "id": "dairy"}} |
Route types are home, product, category, cart, checkout and deals; your app resolves them with its own router. Adds are capped at 5 per line and 5 lines by default (your app can change both caps), and every product must resolve through your product lookup and be in stock. In a call-to-action label, {total} is the sum of the action’s products.
{ "component": "banner", "eyebrow": {"en": "This weekend"}, "title": {"en": "Restock the basics"}, "body": {"en": "Your usual items in one tap."}, "image": "asset://img/basics.png", "cta": { "label": {"en": "Add all for {total}"}, "action": {"type": "add_all", "products": [{"id": "sku_10432"}, {"id": "sku_20877"}, {"id": "sku_31005"}]} }, "fold_on_action": true}Progress
Section titled “Progress”A card with a progress bar towards a basket threshold, such as free delivery. It is bound live to the basket total your app pushes, so it updates as the shopper adds and removes items.
| Field | Required | Meaning |
|---|---|---|
title |
No | Heading above the bar |
tiers |
Yes | [{min_minor, reward_minor}], in ascending order of min_minor |
text |
Yes | Line shown while below the threshold, usually with {gap} |
met_text |
Yes | Line shown once the threshold is reached |
footnote |
No | Small print below the bar |
after_met |
Yes | hold keeps showing the met state; next_tier moves on to the next tier |
style |
No | background, text, accent |
{gap} is the amount still needed for the current tier and never goes below 0. {min} is the tier’s threshold and {reward} its reward.
{ "component": "progress", "title": {"en": "Free delivery"}, "tiers": [{"min_minor": 4000, "reward_minor": 599}], "text": {"en": "Add {gap} more for free delivery"}, "met_text": {"en": "Free delivery is on us"}, "after_met": "hold"}A card with an optional badge, a title and optional body text, tied to a money-bearing treatment such as 10% off the order.
| Field | Required | Meaning |
|---|---|---|
badge |
No | Short label in a pill above the title |
title |
Yes | Main line |
body |
No | Supporting text |
treatment |
Yes | {type, value_milli}: the treatment type and its value in thousandths (10000 is 10, so 10% for a percent_off treatment) |
style |
No | background, text, accent |
{ "component": "offer", "badge": {"en": "This weekend"}, "title": {"en": "10% off this order"}, "body": {"en": "Discount applied to your order."}, "treatment": {"type": "percent_off", "value_milli": 10000}}Message
Section titled “Message”A compact line of text with an optional icon, for confirmations and short notes such as “Discount applied to your order”.
| Field | Required | Meaning |
|---|---|---|
text |
Yes | The message |
icon |
No | star, check or info. Any other value draws no icon |
A message takes its colours from your theme; it has no style field.
{ "component": "message", "text": {"en": "Discount applied to your order"}, "icon": "check"}Preview chip
Section titled “Preview chip”When a component is shown because of a preview pin, the SDK draws a small “Preview” chip over its corner, so a tester can tell pinned content from live content. See the preview links page for Android or iOS.