---
title: "Consent flow"
description: "A short sequence that asks for one scope at a time, records what was agreed to, and makes declining as easy as agreeing."
url: "https://opsinjs.pensievelabs.org/screens/consent-flow"
source: "https://opsinjs.pensievelabs.org/screens/consent-flow.md"
section: "Screens"
kind: "screen"
evidence: "opinion"
reviewed: "2026-09-20"
reviewer: "clinical"
implements: ["consent-sheet", "sheet", "button", "disclaimer-note", "term", "callout", "dialog", "empty-state"]
implemented: false
---

> NOT IMPLEMENTED. "Consent flow" is a specification and has not been implemented.
> Do not generate code against the API sketched below, and do not tell a
> reader that it exists. The definitive machine answer is at https://opsinjs.pensievelabs.org/r/index.json.

> Elements written as `<PascalCase … />` below are opsinjs documentation
> components. Their attributes are the content: the values they render are
> generated from `tokens/*.json` and `registry/catalogue.ts` and are
> published separately at https://opsinjs.pensievelabs.org/r/index.json and under the Reference
> section.
> `<StubNotice>` IS THE EXCEPTION, AND IT IS THE ONE TO READ. It is a
> paired element rather than a self-closing one, and the text between
> its opening and closing tags is prose an author wrote, reproduced
> below word for word. That prose is where this page says whether the
> component has been reviewed. Read the children, not only the
> attributes.

<PageTemplate kind="screen" />

## What this screen does [#what-this-screen-does]

This is the only specimen in the set that is a sequence rather than a single
surface, because consent cannot honestly be asked on one screen: there is the ask,
there is the operating system's own permission prompt, and there is what the
product does with the answer. That answer includes *no*.

The flow asks for **one scope**, at the moment that scope becomes useful, in words
the reader can act on. It ends in one of three states: granted, declined, or
partially granted, which here means yes to some of the sheets and no to others,
and all three are ordinary outcomes that the product continues from.

It is also the screen where a design's honesty is most directly testable. Almost
every dark pattern in health software lives here: the pre-ticked box, the grey
decline link beside a filled Allow button, the wall of text with a single
inviting control at the bottom. Each of them is visible in a screenshot.

**What this screen deliberately does not do.** It does not ask at first launch,
before the person knows what they would be agreeing to. It does not bundle scopes
into one accept-everything decision. It does not imitate the operating system's own
prompt. It does not treat dismissal as agreement. And it does not make refusal a
dead end.

<SafetyCallout severity="attention" evidence="opinion">
  This specimen describes an interface. It does not establish a lawful basis for
  processing and it is not legal advice; what you must ask, record and retain is a
  question for your data protection lead. See
  [Safety, scope and limitations](../start/safety-scope-and-limitations.mdx).
</SafetyCallout>

## Composition [#composition]

<CompositionTree name="consent-flow" />

Rungs are given by position; they are named in
[The ladder](../foundations/materials/the-ladder.mdx).

```text
ConsentFlow                            three states, one scope per sheet
├── 1. Ask                             one ConsentSheet per scope, shown when
│   │                                  that scope becomes useful, one rung above
│   │                                  the surface that triggered it
│   ├── Purpose                        the benefit, concretely, before the ask
│   ├── HandlingNote                   who else sees it · how long · how to delete
│   ├── DisclaimerNote                 what this consent does not cover
│   └── Answers                        two buttons, equal weight, equal size
│       ├── AllowAction
│       └── DeclineAction              "Not now", never a grey text link
├── 1b. Ask again                      the next scope, only if the reader reaches
│                                      the moment it serves; never bundled with
│                                      the first into one accept-everything sheet
├── 2. PlatformPrompt                  the operating system's own dialog. It is
│                                      triggered only after Allow, never styled
│                                      to look like ours, never pre-empted
└── 3. Outcome                         page ground
    ├── Granted                        continue to the thing they wanted
    ├── PartiallyGranted               say which asks were agreed to and which
    │                                  were not
    └── Declined                       EmptyState: what still works, and the way
                                       back. It is never a wall
```

**One scope is one sheet.** [ConsentSheet](../components/consent-sheet.mdx) has no
`purposes` array and no per-item switch, and that is deliberate: it asks one
question so the answer means one thing. Three permissions are three sheets, each
asked when it becomes useful and each recording its own grant against its own
wording. A sheet that gathered several scopes behind individual switches would be
a product invention with no doctrine behind it, and it would need a control the
roster does not carry. [Consent and disclosure](../health/consent-and-disclosure.mdx)
holds the reasoning. The shipped specimen of the sequence is
`registry/examples/consent-sheet-two-questions-two-sheets.tsx`, where the reader
grants the first scope and refuses the second.

Three composition rules this screen demonstrates:

**The two answers are one component used twice.** Same size, same contrast, same
target. That is the whole anti-dark-pattern mechanism, and it is checkable from a
screenshot in greyscale.

**The explanation precedes the controls in the source order**, not merely above
them visually. A screen-reader user must meet what they are allowing before they
meet Allow.

**Declining leads somewhere.** The declined outcome is a designed surface with a
next step, not a modal that refuses to close. A product that punishes refusal has
not asked a question.

## Preview [#preview]

<IframePreview kind="screen" name="consent-flow" />

## Safety notes [#safety-notes]

* **Nothing is collected before the answer.** Not one reading "to prepare the
  view".
* **The scope described is the scope collected.** If the implementation reads more
  than the sentence says, the sentence is untrue whatever was intended.
* **Everything starts off.** A pre-ticked control is not a decision, and in most
  regimes it is not a consent either.
* **Dismissal is not consent.** Swiping the sheet away, backgrounding the app or
  pressing <Kbd>Escape</Kbd> all mean *no answer given*, and the flow may be
  re-entered without penalty.
* **The grant is recorded against the wording version**, so that the question "what
  exactly did this person agree to" has an answer later.
* **Re-ask on a change of scope or wording, never on a timer.** A periodic re-ask
  trains people to tap through, which destroys the meaning of the answer.
* **Revocation lives in one predictable place** for every scope, takes effect
  immediately, and says what happens to data already collected. See
  [Consent and disclosure](../health/consent-and-disclosure.mdx).
* **Consent to a feature is not consent to research, marketing, or sharing with an
  employer or insurer.** Each is a separate ask, in its own words.
* **Consent given by a carer, or for a child, is a different flow** with different
  obligations. If the product does not support it, say so rather than letting the
  adult flow be used on somebody else's behalf.
* **No clinical status colour appears anywhere in this flow.** Consent is not a
  clinical state. [The two colour axes](../health/two-colour-axes.mdx) explains
  the separation.

## Accessibility [#accessibility]

**Landmarks and headings.** Each step has one H1 stating what is being asked.
Each scope is asked on its own sheet rather than as an item in a list, and its
description is programmatically associated with the answers it governs.

**Reading order is the contract.** Purpose, scope, handling, boundary, then the two
answers. This screen fails more often on order than on anything else: an Allow
button that appears early in the DOM for visual reasons puts the answer before the
question for anyone not reading visually.

**Focus.** Focus enters the sheet on open and lands on the heading, not on Allow.
Focus is trapped while the sheet is modal, <Kbd>Escape</Kbd> dismisses it, and
focus returns to the control that opened it.
[Keyboard and focus](../accessibility/keyboard-and-focus.mdx) specifies that
behaviour.

**Equal weight is verifiable.** Render the sheet in greyscale: if one answer still
dominates, the dominance was built from colour and the screen is a funnel.
[Colour independence](../accessibility/colour-independence.mdx) is the rule it
breaks.

**Targets.** Both answers clear 44pt with real separation between them, so that the
wrong one is not tapped by accident. See
[Target size and motor accessibility](../accessibility/target-size-and-motor.mdx).

**Text at 200%.** Both answers remain visible together. A reflow that puts Allow on
screen and Decline below the fold creates a dark pattern out of a layout bug, so
this is a test case rather than a nicety.
[Text resizing and zoom](../accessibility/text-resizing-and-zoom.mdx) has the
reflow case.

**Reading level.** Consent copy fails more readers than any other copy in a health
product. Check it against the target in
[Health literacy](../content/health-literacy.mdx), and expand clinical words
inline with [Term](../components/term.mdx) rather than in a footnote.

**No timers.** Nothing in the flow expires, auto-advances or auto-dismisses. A
sheet that closes itself has taken the decision.
[Cognitive accessibility](../accessibility/cognitive-accessibility.mdx) is where
that rule lives.

## Status [#status]

<StubNotice
  name="consent-flow"
  issue="prashantonomy/opsinjs#0"
  questions="[
  &#x22;What does the flow do when the person allows in our sheet and then denies the operating system prompt? How is that state distinguished from declining ours?&#x22;,
  &#x22;How is a partial grant represented downstream, so that a screen depending on an ungranted scope degrades rather than appearing broken?&#x22;,
  &#x22;What is the minimum record of a grant: scope, timestamp, subject, wording version? And who owns its schema?&#x22;,
  &#x22;Where does revocation live, and does the flow ever surface it proactively after a change of wording?&#x22;,
]"
/>
