Feature paywall + access restriction
Two sibling sub-patterns: (a) **paywall** — what surfaces when a feature is gated by a paid tier the user hasn't subscribed to; (b) **permission restriction** — what surfaces when the user lacks the required role / scope. Modeled after MongoDB LeafyGreen FeatureWalls.
Pattern — Feature paywall / access restriction
*tatus* v0.1.0 — Draft. Koder Stack today is free-tier only, but the moment a tiered plan or per-tenant entitlement lands this spec is needed.
R1 — Hide vs disable vs paywall vs restriction
| Situation | Pattern |
|---|---|
| Feature exists, but user's tier excludes it | *aywall*— show feature with upgrade overlay |
| Feature exists, but user's role lacks permission | *estriction*— show feature with "request access" overlay |
| Feature doesn't exist for the user's deployment at all | *ide*— feature absent from UI entirely |
| Feature transiently unavailable (server, network) | *isable*— show as disabled with explanatory tooltip |
The four are NOT interchangeable. Pick by the decision tree above.
R2 — Paywall anatomy
Overlay on top of the feature surface (semi-transparent backdrop + centered card):
- Lock icon (sparkle-locked variant from icon set).
- Heading:
{Feature name} is part of {Plan name}. - Body: 1–2 sentences explaining the benefit.
- Primary CTA:
Upgrade plan→ billing flow. - Secondary link:
Learn more about plans.
Tone: informative, never pushy. Per specs/content/voice-and-tone.kmd marketing column.
R3 — Restriction anatomy
Overlay or pagelevel message (no semitransparent backdrop — the user should not perceive the feature as "almost reachable"):
- Permission-denied icon (shield).
- Heading:
You don't have access to {feature name}. - Body: 1 sentence:
Ask {admin role} for the {permission slug} permission. - Primary action:
Request access(opens form / email composer withpre-filled context).
- Secondary:
Learn about permissions(link to product docs).
No upsell CTA. Per multitenancy: NEVER reveal crosstenant data even in the restriction message (cross-tenant access = 404, not 403, per specs/multi-tenancy/contract.kmd).
R4 — Accessibility
- Modal overlay: focus trap (Tab cycles inside; Esc dismisses overlay returning user to safe surface).
- Backdrop click on paywall: dismisses overlay (don't trap user).
- Restriction: no Esc behavior since there's nothing to dismiss; user navigates away normally.
- Live-region announce on appearance: "{Feature name} requires {Plan / Permission}".
R5 — Graceful keyboard nav
- Tab order: heading → body → primary CTA → secondary link → close (if any).
- Enter on primary CTA activates.
- Esc on paywall: dismiss (returns to safe surface).
- Esc on restriction: no-op (user uses their browser/system back).
R6 — Internationalization
All strings translatable per specs/i18n/contract.kmd. Keys paywall.{feature}.heading|body|primary|secondary and restriction.{feature}.heading|body|primary|secondary.
Plan and Permission names use their canonical localized form.
Não-escopo
- Billing / entitlement engine (servicesfoundationbilling concern).
- Permission model itself (servicesfoundationidentity concern).
- A/B-tested upgrade copy (product concern).