Design guidelines
How your extensions, copy and marketplace listing fit into CheckCourt
Clubs shouldn't notice where CheckCourt ends and your app begins. A good extension looks like part of the page it sits on: same colors, same font, same tone. This page sums up what we look for.
Part of the review
The review item Erweiterungen entsprechen den Design- und Textregeln (extensions follow the design and copy rules) in Review and marketplace refers to this page. At the end there is a checklist.
Declarative first
For every extension, start with the declarative form. CheckCourt renders it with its own design system, in light and dark mode, on phones and at the kiosk. You don't have to take care of colors, spacing, focus or keyboard, and the review goes faster.
An iframe is only worth it when the blocks really aren't enough: a calendar, a map, a chart, an editing flow with many intermediate steps. On the dashboard, the booking button, the kiosk and the host surfaces (court badges, member list columns, booking hints, plan and sidebar buttons), only the declarative form is allowed anyway.
| You want to … | Use |
|---|---|
| Show numbers, a status, short lists, a few details | declarative |
| Toggle a setting, submit a short form | declarative |
| Trigger an action on a booking | declarative (booking.action) |
| Show a chart, a map, your own calendar | iframe |
| A longer editing flow with several steps | iframe, or a link to your own application |
Where your extension appears
CheckCourt puts every panel in its own card, without your app icon, app name or an „App“
badge, so the card reads like a part of CheckCourt. The only title is the optional label
from your manifest entry; without it the card has
none. Don't bring the name and logo back in the content either. Start right away with
what the person wants to know. Since nothing marks the card as an app's, it must never
imitate CheckCourt's own controls to mislead people, such as a fake sign-in or payment
prompt; the developer terms forbid it. The compact
host surfaces have no card: badges,
cells and hints sit right inside CheckCourt's own screens, so they have to read like
CheckCourt wrote them.
| Point | Context | Space | What fits well |
|---|---|---|---|
booking.detail.panel | Booking details dialog | narrow: the dialog is at most 448 pixels wide, on phones the screen width | two to five details about the booking, at most one button |
booking.action | Button next to the booking's other actions | one label, up to 40 characters | a verb with an object: „Licht einschalten“ (turn on lights), „Platz freigeben“ (release court) |
member.profile.section | Member page in the admin area and the member's settings | full page width | details about the person from your system, one setting |
dashboard.widget | Dashboard, in a two-column grid from tablet width up | half a column, full width on phones | one number or status with one sentence of context |
app.settings | Admin → Apps → your app, below the settings form | full page width | connection status, mappings, test actions |
kiosk.tile | Lower area of the kiosk terminal, at most 30 to 40 percent of the height | read from one to two meters away | one stat and a short line, nothing else |
court.annotation | Court header in the booking plan and court card on the kiosk, one badge line shared by all apps | one badge, up to 24 characters, at most two badges per court | a current state of the court: „Flutlicht an“ (floodlight on), „Gesperrt bis 14:00“ (closed until 14:00) |
member.list.column | Member list in the admin area, at most two app columns | one table cell, up to 24 characters, title up to 20 | one status per member: „Offen“ (open), „Bezahlt“ (paid) |
member.settings.section | Member's own settings page, after Verbundene Apps (connected apps) | full page width | the member's own settings for your app, connection status |
booking.hint | Booking dialog, a fixed-height area above the confirm button | narrow and short, up to 6 display blocks | one sentence about this booking: costs, light, conditions |
booking_plan.action | Header of the booking plan, next to the date navigation | one label, up to 24 characters, one icon | a verb for the plan's day: „Wetter ansehen“ (view weather) |
sidebar.action | Bottom of the sidebar and the mobile navigation, above Abmelden and Hilfe | one label, up to 24 characters, one icon | a verb for something members open often: „Tür öffnen“ (open door) |
nav.page | Main navigation, after the club's own links and before Admin, and a full page of its own (an iframe fills it without a card) | one label, up to 24 characters, one icon; the page is full width | a noun for a place members come back to: „Trainingsplan“ (training plan), „Rangliste“ (ladder) |
Plan for 320 pixels of width as the smallest case. A row wraps on narrow screens,
columns with four to six children wrap to two or three per row, a table with many columns
doesn't.
Kiosk: Nobody is signed in at the kiosk, and anyone can tap. Only show what everyone in the clubhouse may see, and don't offer forms that ask for personal details. The tile reloads every five minutes, so per-second values visibly go stale.
Designing declarative UIs
What each block is for
| Block | For | Not for |
|---|---|---|
heading | Structure, when the panel has several parts. level: 2 for sections, 3 below them | The name of your app, or the card title (label) once more |
text | One sentence of explanation. tone: "muted" for side notes, hints, empty states | Long instructions, advertising |
stat | The one important number: „Öffnungen heute“ (openings today), „Offener Betrag“ (amount due). size: "lg" at most once per panel | Several values of equal weight, use key_value or columns for those |
key_value | Several details about one object: court, time, status | Running text |
list | Items of the same kind: appointments, devices, bookings | Navigation, step-by-step instructions |
badge | A status in one or two words | Decoration, full sentences |
button | An action without input | Navigating to a website, use link for that |
form | A short input with a clear purpose | Long questionnaires, use a link to your application for those |
link | To your application or help, opens a new tab | Actions within CheckCourt |
divider | Separating two parts that really have nothing to do with each other | Between every block |
stack / row | Grouping and placing side by side, for example two stat in a row | Nesting for its own sake |
columns | Values of the same kind in equal widths: forecast days, one stat per court | Unrelated content, more than six values, use list or key_value for those |
Layout order
CheckCourt normalises the order of a declarative document, wherever you put buttons and links:
- Content first: text, headings, stats, lists, badges, key values, forms, in the order you sent them.
- Then one row with all buttons of that level, wrapping on narrow screens.
- Then one row with all links of that level, in green with an external link icon.
So links always sit below the content as their own line, and buttons sit above them. The
rule applies at the top level of the document and again inside every stack. A row
keeps exactly the order you gave it. Consecutive badges share one line.
Write your document in that order anyway, so what you test is what you send. If a button
belongs to one part of the panel, put that part and its button in a stack.
Badge variants
variant | Appearance | For |
|---|---|---|
secondary (default) | gray, filled | neutral states: „Geplant“ (planned), „Inaktiv“ (inactive) |
outline | border only | extra info without judgment: „Halle“ (indoor), „Vorabversion“ (preview) |
default | filled in the primary color | the positive normal case, sparingly: „Verbunden“ (connected), „Bezahlt“ (paid) |
warning | soft amber | needs attention but isn't an error: „Regen möglich“ (rain possible), „Platz nass“ (court wet) |
destructive | red | real problems only: „Fehlgeschlagen“ (failed), „Überfällig“ (overdue) |
The same applies to buttons: outline is the default, default only for the one main
action of a panel, destructive only for deleting and disconnecting.
Limits
A document has at most 100 blocks total, counting nested blocks (a stack with three
children counts as four), and a nesting depth of at most 3, and texts are plain text
without HTML or Markdown. The individual lengths are listed in the
block reference. The limits are upper
bounds, not targets: a good panel gets by with three to eight blocks.
Do and don't: a dashboard widget
{
"ui": "v1",
"blocks": [
{
"type": "row",
"children": [
{ "type": "stat", "label": "Flutlicht heute", "value": "3 Std. 20 Min." },
{ "type": "stat", "label": "Kosten diesen Monat", "value": "48,60 €" }
]
},
{ "type": "text", "text": "Zuletzt eingeschaltet am 06.10.2026 um 19:45 auf Platz 3.", "tone": "muted" }
]
}Two real values (floodlight today, cost this month), one sentence of context (last switched on, date and court), formatted the German way.
{
"ui": "v1",
"blocks": [
{ "type": "heading", "text": "⚡ FLUTLICHT-MANAGER PRO", "level": 2 },
{ "type": "badge", "label": "NEU", "variant": "destructive" },
{ "type": "text", "text": "Willkommen! Hier siehst du alle wichtigen Infos rund um dein Flutlicht. Schnell, einfach und smart!" },
{ "type": "divider" },
{ "type": "stat", "label": "Usage", "value": "3.33 h" },
{ "type": "divider" },
{ "type": "button", "label": "Mehr erfahren!", "action_id": "more", "variant": "default" }
]
}App name as the heading, all caps, an emoji, a red „NEU“ (new) with no actual problem, promotional sentences with exclamation marks („Willkommen! … Schnell, einfach und smart!“: welcome, fast, simple and smart), an English label, a number in English format, a button that says nothing („Mehr erfahren!“: learn more).
Do and don't: a booking panel
{
"ui": "v1",
"blocks": [
{
"type": "key_value",
"pairs": [
{ "label": "Licht", "value": "Ein bis 21:00" },
{ "label": "Tür", "value": "Offen ab 17:45" }
]
},
{ "type": "button", "label": "Licht verlängern", "action_id": "light.extend" }
]
}{
"ui": "v1",
"blocks": [
{ "type": "heading", "text": "Smart Court Control", "level": 2 },
{ "type": "text", "text": "Mit Smart Court Control steuerst du Licht und Tür bequem aus der App. Hier findest du alle Funktionen." },
{ "type": "list", "items": [{ "title": "Licht" }, { "title": "Tür" }] },
{ "type": "button", "label": "Hier klicken", "action_id": "open" }
]
}Describes the app instead of showing the state of the booking. The list (light, door) contains no information, and the button („Hier klicken“: click here) doesn't say what happens.
Host surfaces
Badges, columns and hints sit inside CheckCourt's own screens, next to CheckCourt's own content. They are seen many times a day. Keep them quiet.
Badges on courts
- Short: one to three words, at most 24 characters. „Flutlicht an“ (floodlight on), not „Das Flutlicht ist auf diesem Platz eingeschaltet“.
- Prefer
secondaryoroutline.secondaryfor a state,outlinefor an extra piece of information.defaultsparingly. destructiveonly for real problems that affect play: „Platz gesperrt“ (court closed), „Tür defekt“ (door broken). Not for „Neu“ (new) or for attention.- No emoji, no all caps, no app name.
- Say nothing when there is nothing to say. Leave courts without news out of the list. A badge on every court is noise.
Columns in the member list
- Short title: one word if possible, at most 20 characters: „Beitrag“ (fee), „Lizenz“
(licence). Use the same text as
labelin the manifest so the header doesn't change. - Terse values: one or two words, at most 24 characters. „Offen“ (open), „Bezahlt“ (paid), „Bis 03/2027“ (until 03/2027). No sentences.
- Badge or text: set a
variantonly when the value is a status worth spotting; otherwise plain text reads calmer in a table. - Empty is fine. A member without a value gets an empty cell. Don't fill it with „–“, „k. A.“ or „Keine Daten“ (no data).
Hints in the booking dialog
- One sentence, as a
text. Akey_valueor alinkonly when it really helps. - Inform, don't alarm. The hint never blocks the booking, so don't write it like a warning: „Ab 19:30 brennt das Flutlicht, das kostet 4,50 € pro Stunde.“ (from 19:30 the floodlight is on, at 4.50 € per hour) instead of „ACHTUNG: Flutlichtkosten!“ (warning: floodlight costs!).
- Only when it matters for this booking. Answer
204orhiddenotherwise. A hint on every booking is soon ignored.
Static action labels
booking_plan.action and sidebar.action show a label and an icon from your manifest.
- Verb first, at most 24 characters: „Tür öffnen“ (open door), „Wetter ansehen“ (view weather), „Schaden melden“ (report damage). Not your app name, not „Mehr“ (more).
- Pick an icon that fits the action from the
allowed list:
door-openfor a door,cloud-rainfor weather,wrenchfor damage reports.sparklesis not a default. - The dialog continues the label. Open with what the label promised, not with a welcome text.
Designing iframes
An iframe sits in the same card as a declarative panel. The frame itself has a transparent background and no border. So don't set your own background, borders or shadows around the content, and don't add extra outer spacing: the card already provides it.
Exception: nav.page. An iframe page has no card, no title and no padding. The frame
fills the content area next to CheckCourt's navigation and scrolls itself. Give it the page
padding and the heading yourself, like one of CheckCourt's own pages, and keep the
background transparent so --background shows through.
CheckCourt's color values
On load and on every switch between light and dark mode, CheckCourt sends the
checkcourt:theme message with these CSS variables. The values below are the current
ones, but rely on the message, not on the table.
| Variable | For | Light | Dark |
|---|---|---|---|
--background | Page background, rarely needed | oklch(1 0 0) | oklch(0.145 0 0) |
--foreground | Regular text | oklch(0.145 0 0) | oklch(0.985 0 0) |
--card | Surface of the card your frame sits in | oklch(1 0 0) | oklch(0.205 0 0) |
--card-foreground | Text on --card | oklch(0.145 0 0) | oklch(0.985 0 0) |
--muted | Quiet surfaces: table headers, empty states | oklch(0.97 0 0) | oklch(0.269 0 0) |
--muted-foreground | Secondary text, labels, hints | oklch(0.556 0 0) | oklch(0.708 0 0) |
--primary | Main action, selected state | oklch(0.35 0.1 152) (CheckCourt green) | oklch(0.922 0 0) (light) |
--primary-foreground | Text on --primary | oklch(0.985 0 0) | oklch(0.205 0 0) |
--secondary | Secondary buttons | oklch(0.97 0 0) | oklch(0.269 0 0) |
--secondary-foreground | Text on --secondary | oklch(0.205 0 0) | oklch(0.985 0 0) |
--accent | Hover and highlight in lists | oklch(0.97 0 0) | oklch(0.269 0 0) |
--accent-foreground | Text on --accent | oklch(0.205 0 0) | oklch(0.985 0 0) |
--destructive | Errors, deleting, overdue items | oklch(0.577 0.245 27.325) | oklch(0.704 0.191 22.216) |
--warning | Warning text and icons: attention, not an error | oklch(0.5 0.12 58) | oklch(0.85 0.15 85) |
--warning-foreground | Text on a solid --warning surface | oklch(0.985 0 0) | oklch(0.28 0.07 50) |
--warning-muted | Soft warning surface behind --warning text, as in warning badges | oklch(0.962 0.059 95.6) | oklch(0.3 0.06 70) |
--border | Lines and borders | oklch(0.922 0 0) | oklch(1 0 0 / 10%) |
--input | Input field borders | oklch(0.922 0 0) | oklch(1 0 0 / 15%) |
--ring | Focus ring | oklch(0.708 0 0) | oklch(0.556 0 0) |
--radius | Base corner radius | 0.625rem | 0.625rem |
Green isn't always green
In dark mode, --primary is almost white. If you hard-code CheckCourt green, dark mode
gets a color that appears nowhere else there. Always use var(--primary).
CheckCourt derives radii from --radius: small elements like buttons roughly
calc(var(--radius) * 0.8), cards and lists var(--radius). No font is passed along.
CheckCourt uses Inter. Load it yourself or use the system font, with text at
14 pixels and secondary text at 12 pixels. The frame has an opaque origin, so serve font
files with Access-Control-Allow-Origin: *, otherwise the browser blocks them (see
iframe extensions).
Applying them
Set light mode defaults so nothing flashes before the first message arrives. The script
from UI extensions then
overrides them and sets the dark class.
:root {
--foreground: oklch(0.145 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--primary: oklch(0.35 0.1 152);
--primary-foreground: oklch(0.985 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--radius: 0.625rem;
color-scheme: light;
}
:root.dark {
color-scheme: dark;
}
body {
margin: 0;
background: transparent;
color: var(--foreground);
font: 14px/1.5 Inter, ui-sans-serif, system-ui, sans-serif;
}
.hint {
color: var(--muted-foreground);
font-size: 12px;
}
.button {
min-height: 36px;
padding: 0 12px;
border: 1px solid var(--border);
border-radius: calc(var(--radius) * 0.8);
background: transparent;
color: var(--foreground);
}
.button--primary {
border-color: transparent;
background: var(--primary);
color: var(--primary-foreground);
}
.button:focus-visible,
input:focus-visible {
outline: 3px solid color-mix(in oklch, var(--ring) 50%, transparent);
outline-offset: 1px;
}
input {
border: 1px solid var(--input);
border-radius: calc(var(--radius) * 0.8);
background: transparent;
color: var(--foreground);
}
.error {
color: var(--destructive);
}
@media (prefers-reduced-motion: reduce) {
* {
animation: none !important;
transition: none !important;
}
}Test every view in both modes. Images and charts designed on white need their own version for dark mode, or a transparent background.
Behavior in the frame
- Height: Report it with
checkcourt:resizewhenever the content changes. No scroll area of your own in the frame, except for really long lists. Anav.pageframe is the exception: it always fills the page, ignoresresizeand scrolls its own document. - No popups: No
alert(),confirm(), custom modal dialogs over the frame or new windows to confirm something. Confirm inside the frame itself or withcheckcourt:toast. - Navigation only via
checkcourt:navigate: For targets in CheckCourt, send the message instead of linking to CheckCourt URLs. Links to your own application open a new tab (target="_blank"). - Notifications via
checkcourt:toast: Short, one sentence,successfor confirmations,errorfor errors the person can fix themselves. - No login page in the frame: Sign the person in via the context token, not with a login form of your own.
Copy
CheckCourt addresses members and board members in German, with the informal du, calm and matter-of-fact. Your copy should sound as if the same team wrote it.
- German, including labels and error messages. Use jargon only where clubs use it themselves („Flutlicht“ (floodlight), „Medenspiel“ (league match), „Platzwart“ (groundskeeper)).
- Du, never the formal Sie. For instructions, the infinitive is often enough: „Platz auswählen“ (select court).
- Sentence case: „Offene Rechnungen“ (open invoices), not „Offene Rechnungen Im Überblick“ (title-cased) and not „OFFENE RECHNUNGEN“.
- No dashes, meaning no long dashes in the middle of a sentence. Split the sentence, or use a colon or a comma.
- No exclamation marks and no marketing speak. „Erfolgreich gespeichert!“ (successfully saved!) becomes „Gespeichert“ (saved), „Jetzt smart durchstarten!“ (get smart now!) is dropped.
- Buttons are verbs: „Licht einschalten“ (turn on lights), „Rechnung senden“ (send invoice), „Verbindung trennen“ (disconnect). Not „OK“, „Weiter“ (next), „Hier klicken“ (click here), „Mehr erfahren“ (learn more).
- Short: labels at most three or four words. One sentence of explanation is almost always enough.
- German number and date formats:
07.10.2026,18:30in 24-hour format,1.250,50 €,3 Std. 20 Min.(3 h 20 min). Weekdays written out or as „Mo“, „Di“ (Mon, Tue). - Empty states give the reason: „Noch keine Öffnungen heute.“ (no openings yet today) instead of „Keine Daten“ (no data).
- Errors say what to do now: „Die Tür antwortet nicht. Versuch es in ein paar Minuten erneut.“ (the door isn't responding, try again in a few minutes) instead of „Error 503“.
| Don't | Do |
|---|---|
| „Super! Deine Buchung wurde erfolgreich synchronisiert!“ (Great! Your booking was successfully synced!) | „Buchung übertragen“ (booking transferred) |
| „STATUS“ above a value | „Status“ |
| „Mehr erfahren →“ (learn more) | „Rechnung öffnen“ (open invoice) |
| „Fehler: undefined“ (error: undefined) | „Die Daten konnten nicht geladen werden. Versuch es später erneut.“ (the data couldn't be loaded, try again later) |
| „10/7/2026, 6:30 PM“ | „07.10.2026, 18:30“ |
Brand and colors
- CheckCourt green is the only accent color, specifically as
--primary. Don't bring your own brand color, gradients or a second accent color into your extension. - No hard-coded color values. No hex, no
rgb(), nooklch()in your extension's code, only the variables. That's the only way dark mode works. - No per-club look. CheckCourt looks the same in every club. Don't color your extension in club colors, even if you know them.
- Your logo only as the app icon in the marketplace and under Admin → Apps. Extensions show no app icon, and your content has no place for a logo, wordmark or banner either.
- Icons sparingly, as line icons in the style of Lucide, in the
text color. The easiest way is to use Lucide itself. In declarative blocks,
icontakes only the allowed names, and CheckCourt draws them for you. Use an icon where it carries meaning, such as the weather of a forecast day, not in front of every line.
Don't look like a template kit
Signs that a UI was assembled without care stand out in the review:
- No all-caps headings above sections („ÜBERSICHT“, „STATUS“).
- No emoji, not in text, headings or badges.
- No icons in colored boxes as decoration in front of every line.
- No hairlines between everything. A
dividerseparates parts, not rows. - Show the real objects instead of describing them. Instead of „Hier siehst du deine Platzbelegung“ (here you can see your court occupancy), show the occupancy itself. Instead of „3 Geräte verbunden“ (3 devices connected) as a sentence, a list of the three devices and their state.
Accessibility
Declarative extensions come with this built in. For iframes:
- Contrast through the variables.
--foregroundon--cardand--primary-foregroundon--primaryare tested. Your own combinations, such as--muted-foregroundon--mutedfor important text, are not. - Visible focus for everything clickable, using
--ring. Never removeoutlinewithout a replacement. - Keyboard operable: every action is reachable with Tab and can be triggered with Enter or Space, in a sensible order, without keyboard traps.
- Real controls:
<button>for actions,<a>for links, no<div>with a click handler. - Labeled fields: every input has a
<label>. A placeholder doesn't replace it. - Large enough targets: at least 24 × 24 pixels, 44 × 44 pixels on touch devices.
- Reduce motion when
prefers-reduced-motionis set. Nothing blinks or loops endlessly. - No autoplaying media, neither video nor sound.
- Meaning not by color alone: a red value also needs a word like „Überfällig“ (overdue).
Behavior
- Fast: Declarative responses have 2 seconds. Keep values cached in your system instead of querying third parties on every request. An iframe shows something useful within one second and loads the rest afterwards.
- Cache deliberately: CheckCourt reuses a render for 30 seconds unless you say
otherwise. Set
cache.max_ageto0for live values such as a door state, and longer (up to 300 seconds) for content that rarely changes. - Hide what isn't relevant: If your panel has nothing to say about this booking,
member or club, hide it with
{ "ui": "v1", "hidden": true }or204 No Content. No card is better than a card that says nothing. - Calm empty states where absence is information: If the empty state itself tells
the person something, keep the card with a single
textwithtone: "muted", such as „Heute keine Termine“ (no appointments today) in a widget people check every day, or „Keine offenen Beiträge“ (no open fees) on a profile. An empty document leaves an empty card, so don't send one. - Calm errors: If your service can't deliver part of the information, show the rest and one sentence about the missing part. If everything fails, CheckCourt shows „Dieser Inhalt ist gerade nicht verfügbar.“ (this content is currently unavailable), which is better than a half answer with a stack trace.
- No tracking, no ads: no tracking pixels, no third-party analytics scripts, no advertising, no references to your other products.
- Only what the person may see: Check the
capabilitiesin the context token. Only show personal details about others when, for example,can_see_booking_detailsorcan_see_member_detailsis set, and only show controls when the person is allowed to perform the action. Whatever CheckCourt hides from a person, your extension must not show.
The marketplace listing
The fields and their limits are listed in Review and marketplace. For the design:
Icon: Square, at least 128 × 128 pixels, CheckCourt stores it as WebP at 256 × 256 pixels. A simple mark that stays recognizable at 20 pixels. No text, no screenshot, no fine lines. It must work on light and dark backgrounds: use a filled shape with your mark on it, or test a cut-out mark on both backgrounds.
Screenshots: Up to 5, PNG, JPEG or WebP, at most 2 MB each. CheckCourt scales them down to at most 2,000 pixels on the longer side. Show your app where it appears in CheckCourt, with the German UI and in the sandbox club. The first screenshot is the most important: it shows what the app does for the club. Use made-up names, no real members, and no frames, arrows or promotional text in the image.
Name: 2 to 60 characters, the name of your product. No additions like „für CheckCourt“ (for CheckCourt), „Pro“ or „Nr. 1“ (No. 1), and no all caps unless the name is really written that way.
Short description: One sentence of up to 140 characters on what the app does for the club. „Schaltet das Flutlicht passend zu den Buchungen und rechnet den Verbrauch ab.“ (switches the floodlights to match bookings and bills the usage) instead of „Die smarte Lösung für modernes Platzmanagement!“ (the smart solution for modern court management!).
Description: Written for club boards, not for developers. What the app does, what the club needs for it, what it costs. Paragraphs instead of bullet lists full of buzzwords.
Category: The one where a board member would look first.
| Category | For |
|---|---|
| Anlage (facilities) | Courts, lights, doors, irrigation, maintenance |
| Finanzen (finance) | Fees, invoices, bookkeeping, payments |
| Spielbetrieb (match play) | Teams, tournaments, rankings, training |
| Kommunikation (communication) | Newsletters, messages, calendars, club website |
| Persönlich (personal) | Apps a member connects for themselves |
Before you submit
This list follows the support team's review checklist.
Zweck passt zu den Rechten (purpose matches the permissions)
- Every scope in the manifest is needed by a feature described in the listing.
Datenschutzangaben vollständig, AVV vorhanden, falls nötig (privacy details complete, data processing agreement in place if needed)
- Data categories, purpose and storage location in the manifest match what the app does.
- The extensions only show what the person may see according to
capabilities. - No tracking pixels, no third-party analytics scripts, no advertising.
Im Sandbox-Verein getestet (tested in the sandbox club)
- Every extension responds in under 2 seconds and either hides itself or has a calm empty state when there's nothing to show.
- Every extension works in light and dark mode and at 320 pixels of width.
- Kiosk tiles are readable from two meters away.
- Court badges, column cells and booking hints were checked with a slow and a failing app: nothing jumps, nothing is shown when the app fails.
Erweiterungen entsprechen den Design- und Textregeln (extensions follow the design and copy rules)
- Declarative where it's enough. iframes only with a reason.
- No hard-coded color values, only the variables. No accent color of your own.
- No app name and no logo in the card content, and nothing that imitates CheckCourt's own controls to mislead.
- German, du, sentence case, no dashes, no exclamation marks.
- Buttons are verbs, dates and numbers formatted the German way.
- No all-caps headings, no emoji, no decorative icons.
- Badges short and calm,
destructiveonly for real problems. Column values terse, hints one sentence without alarm. - Static action labels start with a verb, at most 24 characters, with a fitting icon.
- In iframes: visible focus, everything keyboard reachable, fields labeled, no
popups, navigation only via
checkcourt:navigate.
Eintrag (listing)
- Icon without text, recognizable on light and dark backgrounds.
- Screenshots from CheckCourt, in German, without real people.
- Short description in one matter-of-fact sentence.