---
title: "Ask for consent before collecting"
description: "Ask for one category of health data at the moment it becomes useful, in words the reader can act on, with declining as easy as agreeing."
url: "https://opsinjs.pensievelabs.org/recipes/consent-before-collection"
source: "https://opsinjs.pensievelabs.org/recipes/consent-before-collection.md"
section: "Recipes"
kind: "recipe"
evidence: "opinion"
reviewed: "2026-09-20"
reviewer: "clinical"
implements: ["consent-sheet", "field"]
---

> 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.
> Nothing is missing from this page. The data simply does not live in
> the prose.

<PageTemplate kind="recipe" />

## The task [#the-task]

Your product wants to start collecting something about somebody's body: heart rate
from a watch, sleep from a phone, location alongside symptoms, a menstrual cycle,
a medication list. Before it does, you have to ask, in a way that a person can
understand and refuse.

Consent in a health product is not a legal formality bolted on at first launch. It
is a design surface with a specific failure mode: the wall of text with one
inviting button, agreed to by everybody and understood by nobody. That surface is
compliant-looking and worthless. It is worthless to the reader, who has not
made a decision, and worthless to you, because a consent nobody understood is a
consent that evaporates the moment it is examined.

This recipe builds the honest version: one category, asked at the moment it
becomes useful, in a sentence that says what you get and what they get, with
declining as easy as agreeing and revocation always one predictable place away.

<SafetyCallout severity="attention" evidence="opinion">
  This recipe is about the interface. It is not legal advice and it does not make
  your product compliant with UK GDPR, the EU GDPR, HIPAA or any other regime. What
  you must ask, what you must record and how long you may keep it are questions for
  your data protection lead. opsinjs supplies the surface, never the obligation.
  See [Safety, scope and limitations](../start/safety-scope-and-limitations.mdx).
</SafetyCallout>

## What you need [#what-you-need]

The seven components this surface composes are `shipped`: the code exists and
installs, and each may change its API in any release. Every opsinjs component has
been audited against WCAG 2.2 AA by its own authors, not by an independent
reviewer, and no component has had a clinical review, so nothing here is for a
production health surface until a clinician signs it. See
[ADR 0025](../project/decisions/0025-the-audit-is-author-run.mdx). The per-item
toggles in the last row, `switch` and `checkbox`, install as source too, and you
wire their state.

| Part                | Component                                           | Status                           | What it does here                                                 |
| ------------------- | --------------------------------------------------- | -------------------------------- | ----------------------------------------------------------------- |
| The consent surface | [ConsentSheet](../components/consent-sheet.mdx)     | <StatusBadge status="shipped" /> | Granular, revocable, recorded consent for one scope.              |
| The presentation    | [Sheet](../components/sheet.mdx)                    | <StatusBadge status="shipped" /> | Detents, focus handling, dismissal that does not imply an answer. |
| The choice          | [Field](../components/field.mdx)                    | <StatusBadge status="shipped" /> | Label, control and description for each grantable item.           |
| The two answers     | [Button](../components/button.mdx)                  | <StatusBadge status="shipped" /> | Agree and decline, of equal weight.                               |
| Unfamiliar words    | [Term](../components/term.mdx)                      | <StatusBadge status="shipped" /> | Plain-English expansion, inline, not in a footnote.               |
| The boundary        | [DisclaimerNote](../components/disclaimer-note.mdx) | <StatusBadge status="shipped" /> | What this consent does not cover.                                 |
| Context             | [Callout](../components/callout.mdx)                | <StatusBadge status="shipped" /> | Information about the request; never on the status axis.          |
| Per-item toggles    | `switch` · `checkbox`                               | <StatusBadge status="shipped" /> | Individual grant toggles; both install as source.                 |

Tokens, by family:

| Family             | Where it applies                                                      | Reference                                                         |
| ------------------ | --------------------------------------------------------------------- | ----------------------------------------------------------------- |
| Type scale         | Body text at a size that is genuinely readable, not the legal minimum | [Type scale](../foundations/typography/type-scale.mdx)            |
| Space scale        | Equal target size and separation for both answers                     | [Density and touch](../foundations/space/density-and-touch.mdx)   |
| Material rung      | The sheet's rung and its opaque fallback                              | [Choosing a layer](../foundations/materials/choosing-a-layer.mdx) |
| `--opsin-status-*` | Nowhere. Consent is not a clinical state                              | [The two colour axes](../health/two-colour-axes.mdx)              |

## Build it [#build-it]

<Steps>
  ### Name exactly what you want, per category [#name-exactly-what-you-want-per-category]

  "Health data" is not a scope. "Your resting heart rate and sleep, from your watch"
  is. Write the list of what you will actually read, at the granularity the person
  would recognise, and ask for that.

  If the list is long enough to need scrolling, you are asking for too much at once.
  Split it. The categories in
  [Category identity](../health/category-identity.mdx) are a reasonable seam,
  because they are the seams the reader already thinks in.

  ### Ask at the moment of value, not at first launch [#ask-at-the-moment-of-value-not-at-first-launch]

  A consent screen shown before the person knows what the product does is a toll
  gate. They will agree to pass it and remember nothing.

  Ask when the reader is about to get something for agreeing: when they open the
  sleep section for the first time, when they tap "connect my watch", when they ask
  for the trend that needs the data. The request then explains itself, and a refusal
  is genuinely informative to you rather than being noise.

  ### Separate your consent from the platform's permission [#separate-your-consent-from-the-platforms-permission]

  The operating system's health-data prompt and your consent are different things
  asked by different parties, and they must not be blurred into one. Ask yours
  first, in your own words, and only then trigger the platform prompt. That way a
  person who declines yours is never shown a system dialog they did not expect,
  and a platform grant is never mistaken for agreement with you.

  Never present your own screen styled to look like the system's. That is
  impersonation, and it is the clearest possible signal of bad faith.

  ### One decision per screen, defaulted to off [#one-decision-per-screen-defaulted-to-off]

  Nothing is pre-ticked. Every grantable item starts off, and the person turns on
  what they want. A pre-ticked box is not a decision, and in most regimes it is not
  a consent either.

  If there are several items, they are individually grantable. Bundling is "agree
  to all of this or none of it". It converts a considered choice into an
  ultimatum, and the person who wanted to share sleep but not location is forced
  into the wrong answer.

  ### Make declining exactly as easy as agreeing [#make-declining-exactly-as-easy-as-agreeing]

  This is the check that catches dark patterns, and it is testable. Both answers
  are: the same component, at the same size, with the same contrast, in an order
  that does not bury one, and reachable by the same number of interactions. If your
  decline is a grey text link under a large filled button, you have designed a
  funnel, not a question.

  Declining must not be a dead end either. Say what still works without the data,
  and let the person continue to it. If the honest answer is that the feature cannot
  work at all, say that plainly and offer the way back.

  ### Say what happens to the data [#say-what-happens-to-the-data]

  Four sentences, in the reader's words, before the buttons:

  * **What you collect**, precisely.
  * **What it is used for.** State the benefit to them concretely: "so we can show
    your sleep next to your resting heart rate".
  * **Who else sees it.** If the answer is nobody, say nobody. If it is a named
    third party, name them.
  * **How long you keep it, and how to delete it.**

  If any of those four is uncomfortable to write plainly, the discomfort is
  information about the practice, not about the copy.

  ### Record the grant against the wording [#record-the-grant-against-the-wording]

  Store what was granted, when, by whom, and **which version of the wording** they
  saw. Without the last one you cannot answer the only question that ever gets asked
  later: what exactly did this person agree to?

  That record is also what makes re-asking honest. You re-ask when the wording or
  the scope changes, because the previous agreement was to different terms, and not
  on a timer, because a timer just trains people to tap through.

  ### Put revocation where they will look for it [#put-revocation-where-they-will-look-for-it]

  The same place, always, and the same place for every category: one settings
  surface listing what is on, what it is used for, and a control to turn it off.
  Turning it off takes effect immediately and says what happens to the data already
  collected.

  A consent you cannot find how to withdraw is a consent in name only.
</Steps>

## The copy [#the-copy]

Second person, present tense, short sentences, and the benefit before the ask.

> **To show you `<the thing they want>`, we need to read `<the precise data>` from `<the source>`.**
> &#x2A;*We use it only to `<the specific purpose>`. `<Who else sees it.>`**
> &#x2A;*We keep it `<how long>`. You can turn this off at any time in Settings.**

<DoDont>
  <DoDont.Do>
    "To show your sleep next to your resting heart rate, we need to read sleep and
    heart rate from your watch. We use them only to draw your trends. Nobody else
    sees them. You can turn this off at any time in Settings." **Allow** and
    **Not now** are two buttons of equal weight.
  </DoDont.Do>

  <DoDont.Dont>
    "We care about your privacy. By continuing you agree to our processing of your
    health data as described in our Privacy Policy." **Continue** is filled and
    large, and **Skip** is small grey text. Nothing specific has been asked, and
    one answer has been made to look like the mistake.
  </DoDont.Dont>
</DoDont>

**Button labels name the outcome**, not agreement: "Allow sleep data" and "Not
now", rather than "I agree" and "Cancel". "Cancel" on a consent screen is
ambiguous. Does it cancel the sharing, or cancel the reading?

**Never make refusal sound reckless.** "No thanks, I don't want better results" is
coercion in the shape of a button, and it is the clearest tell that a product's
consent flow was designed as a conversion funnel.

**Avoid jargon in both directions.** Not "data processing", not "telemetry", and
also not a euphemism: "we read your heart rate" is what is happening, so write
that. Where a clinical word is unavoidable, wrap it in a
[Term](../components/term.mdx).

Wider guidance on asking uncomfortable questions gently is in
[Asking sensitive questions](../content/asking-sensitive-questions.mdx).

## Get it right [#get-it-right]

**Safety**

* Nothing is collected before the answer. Not one reading "to prepare the view".
* The scope described is the scope collected. If engineering reads more than the
  sentence says, the sentence is a lie regardless of intent.
* Every item is individually grantable and individually revocable.
* The record includes the wording version. Re-ask on a change of wording or scope,
  never on a schedule.
* Declining leaves a working product and a visible route back.
* Consent for a child, or by a carer for somebody else, is a different flow with
  different obligations. If your product supports it, design it deliberately; if it
  does not, say so rather than letting somebody use the adult flow on a child's
  behalf.
* Consent to use a feature is not consent to be contacted, to research, or to
  share with an employer or insurer. Each is a separate ask.
* Health data on a shared or lock screen is visible to whoever is nearby. What you
  reveal after consent is granted is its own decision.

**Accessibility**

* The full explanation is reachable in reading order **before** the buttons. A
  screen-reader user must not meet "Allow" before they meet what they are
  allowing.
* Both answers are the same size, contrast and prominence. Check with the page
  in greyscale, where a visual hierarchy built purely on colour becomes obvious.
* Every target clears 44pt with real separation. See
  [Target size and motor accessibility](../accessibility/target-size-and-motor.mdx).
* Focus enters the sheet on open and returns on close;
  <Kbd>Escape</Kbd> dismisses it and dismissal means **no answer given**, never an
  implied yes. The full focus contract is in
  [Keyboard and focus](../accessibility/keyboard-and-focus.mdx).
* At 200% text nothing is clipped and both buttons remain visible together; a
  layout where agreeing is on screen and declining is below the fold is a dark
  pattern created by reflow.
* Reading level is checked against the target in
  [Health literacy](../content/health-literacy.mdx). Consent copy fails more
  readers than any other copy in a health product.
* Nothing in the flow is timed. A person may take as long as they like, and a
  sheet that dismisses itself has taken the decision for them.
  [Cognitive accessibility](../accessibility/cognitive-accessibility.mdx) covers
  time limits.

## Variations [#variations]

**Re-consent after a change.** Do not re-ask from scratch. Say what has changed
since they last agreed, show the new wording, and let them re-decide only the part
that moved.

**Consent inside a questionnaire.** A question that could disclose something
serious needs its own handling, and the disclosure that follows matters more
than the consent that preceded it. Self-harm, abuse and a diagnosis are
examples. Treat it as a distinct flow.

<Todo>
  Disclosure inside a questionnaire, and signposting to crisis support, are not
  covered by any recipe. They are the highest-stakes surfaces a consumer health
  product has, and they need clinical review rather than a design pattern.
</Todo>

**Consent to share with a clinician.** A different scope, a different audience and
usually a different retention period. See
[Sharing with a clinician](../patterns/sharing-with-a-clinician.mdx).

**Consent for research.** A separate regime with its own requirements. Never
bundled with product consent, never implied by continued use.

**Withdrawal.** Not a variation of asking but its mirror, and the part that is
usually unbuilt. Turning something off says what stops, what is deleted, what is
kept and why.

## Related [#related]

* **[Consent and disclosure](../health/consent-and-disclosure.mdx)** is the
  doctrine: granular, revocable, recorded, re-askable.
* **[Consent and permissions](../patterns/consent-and-permissions.mdx)** covers
  the same task at flow scale, including the platform prompt sequencing.
* **[Consent flow](../screens/consent-flow.mdx)** is the whole-screen specimen
  this recipe composes.
