---
title: "On-screen privacy"
description: "Health data is read on trains, in offices and over shoulders. The page sets how much of it an interface exposes to the room around the reader."
url: "https://opsinjs.pensievelabs.org/health/on-screen-privacy"
source: "https://opsinjs.pensievelabs.org/health/on-screen-privacy.md"
section: "Health"
kind: "health"
evidence: "opinion"
reviewed: "2026-09-02"
reviewer: "design"
aliases: ["shoulder surfing", "hide values", "screenshot", "privacy screen", "app switcher"]
implements: ["value", "result-card", "metric-tile", "sheet", "dialog", "empty-state"]
---

> 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="health" />

## What this means [#what-this-means]

Privacy engineering in health products concentrates on what is invisible:
encryption, access control, retention periods and sharing agreements. All of it
is necessary, and none of it governs the disclosure this page is written against,
which happens when a reader opens the app on a train with someone sitting next to
them.

Everything rendered on a screen is visible to anyone with line of sight to the
device, and three common layout conventions widen that exposure rather than
narrowing it: the most sensitive number is rendered at the largest size, at the
highest contrast, and in the position the eye reaches first. A condition name in
a section header is legible from a metre away, and an app icon carrying a badge
on a lock screen discloses that the app has something waiting, without the app
being opened at all.

A second exposure sits on a surface the app does not draw. When someone
backgrounds an app, the operating system captures a snapshot of the current
screen and shows it in the multitasking view, where it persists and where other
people looking at the device will see it.

## The rule [#the-rule]

**Sensitive values are hidden by default where the reader has asked for that, the
app never leaks its content through system surfaces, and re-authentication is
required to reveal.**

Every requirement below is the product's to implement, because no opsinjs
component carries a masking, hiding or sensitivity prop today. "Components that
implement this" states that gap in full.

| #  | Requirement                                                                                                                                                                                                                                                                                    | Surface it governs                                                                  | What breaks if it is skipped                                                                                                                               | Where it is specified in full                                                                                                              |
| -- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| 1  | **A per-user privacy setting that is easy to find.** The setting is reachable without a search through nested preferences, the private behaviour is the default rather than something the reader switches on, and categories the reader has marked sensitive start at the more private option. | The settings surface, and every screen rendering a category marked sensitive        | A reader who wants masking never finds the control, and a default of visible protects only the readers who already knew to look for it.                    | Health data's standing as a special category under the UK and EU GDPR is set out in [Consent and disclosure](./consent-and-disclosure.mdx) |
| 2  | **Hidden values are masked rather than obscured.** The value is replaced by a fixed-width mask, never by a blurred, shortened or otherwise distorted rendering of the real thing.                                                                                                              | The value itself, at every size it is rendered                                      | A blurred number can often be read and a shortened one can be inferred from its length, so the value is disclosed by a treatment that looks protective.    |                                                                                                                                            |
| 3  | **Layout does not shift on reveal.** A masked value occupies the same space as the real one.                                                                                                                                                                                                   | The layout of the card, row or tile holding the value                               | Revealing reflows the screen, and the presence or absence of a value becomes information in its own right.                                                 |                                                                                                                                            |
| 4  | **The label is hidden where the label is the disclosure.** For sensitive categories the section heading is often more revealing than the number, and it is hidden alongside the value.                                                                                                         | Section headings, category names and any other text naming what is measured         | The number is masked while the category stays legible, so the disclosure the reader asked to prevent happens anyway.                                       |                                                                                                                                            |
| 5  | **Content is blurred or replaced on backgrounding.** The cover is applied before the system snapshot is taken and removed when the app returns to the foreground.                                                                                                                              | The app switcher, and the system snapshot behind it                                 | The snapshot holds a readable screen for as long as the card persists in the multitasking view, in front of anyone who picks the device up.                |                                                                                                                                            |
| 6  | **Reveal requires re-authentication.** Revealing a hidden value uses the platform's biometric or passcode flow with a timeout short enough to matter, and a non-biometric route is always offered, because biometrics fail and not everyone can use them.                                      | The reveal control on every masked value                                            | Anyone holding the unlocked phone can reveal the values, and a biometric-only route locks out readers whose biometrics fail or who cannot use them at all. |                                                                                                                                            |
| 7  | **Screenshots are treated as a user right.** Capture is never blocked, a warning is shown once when a sensitive screen is captured, and an intentional redacted share is the easier of the two paths.                                                                                          | The platform capture action, and the product's own share flow                       | A block removes a legitimate route for sending a result to family or a clinician, and the reader reaches for a second phone instead.                       |                                                                                                                                            |
| 8  | **Notifications, badges and widgets are on-screen privacy surfaces.** They carry health content to surfaces the app does not control and are governed by the same reasoning as the screen.                                                                                                     | The lock screen, the notification shade, the app icon badge and home-screen widgets | Content masked inside the app is published in full to a lock screen, which displays notification content without authentication by default.                | [Notifications and off-screen alerts](./notifications-and-off-screen-alerts.mdx)                                                           |
| 9  | **Reveal is per value and per session.** A reveal applies to the value revealed and expires with the session rather than persisting by default.                                                                                                                                                | The reveal state held across values and across launches                             | A reader who revealed one number on a train finds every number revealed on the next launch, in whatever room that launch happens in.                       |                                                                                                                                            |
| 10 | **The app is usable with values masked.** No reveal is ever required to use the app.                                                                                                                                                                                                           | Navigation, data entry and settings, on every screen                                | Masking becomes a cost the reader pays in function, so they switch it off and it protects nobody.                                                          |                                                                                                                                            |
| 11 | **The shoulder-surfing case is checked in review.** The review asks what is legible at a metre and at an angle.                                                                                                                                                                                | The design review, for every screen showing a sensitive category                    | The legibility of a screen to a second person is never tested, so the ten requirements above hold only where someone happened to think of them.            |                                                                                                                                            |

The last column is filled only where another page carries the requirement, or
the material it depends on, in full. Rules 2 to 7 and 9 to 11 have no such
destination and their cells are left empty rather than pointed at a page that
does not cover them. Rules 2, 3 and 4 have no component prop that implements
them today, which the note at the end of this page records.

## Why (evidence) [#why-evidence]

<ResearchNote evidence="opinion" date="2026-09-02">
  **Opinion.** We cite no study, and the reasoning is a threat model rather than
  a finding: a second person with line of sight to the device, and a device whose
  content outlives the reader's attention to it.

  Two behaviours are platform facts rather than opinions, and both are worth
  verifying against current platform documentation, because a copy held in a
  design system goes out of date as the platforms change. Mobile operating
  systems capture a snapshot of the visible screen when an app is backgrounded
  and display it in the multitasking interface, and lock screens display
  notification content without authentication by default. Rule 5 exists because
  of the first and rule 8 because of the second.

  Rule 2, which requires a mask rather than a blur, is a small technical point
  with a real consequence. A blur is a lossy transform of the real value and,
  depending on radius and font, is sometimes readable and often guessable,
  particularly for short numeric strings. A mask carries no information about the
  value standing behind it. The same applies to any reveal-on-hover treatment,
  which additionally does not exist on touch.

  Rule 7 forbids blocking screenshots, against the instinct that a block is the
  protective choice. Sharing a result with a family member or a clinician is a
  legitimate and frequent need, and a block does not remove the need: it pushes
  the reader to photograph the screen with a second phone, and the product has
  no further say in what happens to that image.

  What would change our mind: a field measurement of whether a warning shown on
  screenshot changes what readers do next would revise rule 7's warning clause,
  which we hold weakly because we expect the warning mostly generates annoyance.
  Holding it weakly means the page requires what rule 7 already states and no
  more: warn once on a sensitive screen, never block the capture, and never
  repeat the warning on subsequent captures.
</ResearchNote>

## Applying it [#applying-it]

<DoDont>
  <DoDont.Do>
    Render a masked value as a fixed-width block of the same size and weight as
    the real value, with an explicit reveal control that has an accessible name:
    "Show your result".
  </DoDont.Do>

  <DoDont.Dont>
    Apply a CSS blur to the number. A blur is often legible at a glance to
    someone sitting beside the reader, it is a lossy transform that still
    carries the real value to anyone who captures it, and it is trivially
    removed from a screenshot.
  </DoDont.Dont>
</DoDont>

<DoDont>
  <DoDont.Do>
    Cover the whole window with an opaque screen on `resign active`, before the
    snapshot, and remove it on return.
  </DoDont.Do>

  <DoDont.Dont>
    Leave the results screen live in the app switcher. The snapshot is then
    visible to anyone who picks up the phone, for as long as the card persists in
    the multitasking view.
  </DoDont.Dont>
</DoDont>

<DoDont>
  <DoDont.Do>
    Collapse "Sexual health" to "Results", with the category name hidden, when
    the reader has marked that category sensitive.
  </DoDont.Do>

  <DoDont.Dont>
    Mask the number and leave the heading. A person beside the device reads the
    category from the heading, which for a sensitive category discloses more than
    the value does.
  </DoDont.Dont>
</DoDont>

<DoDont>
  <DoDont.Do>
    Offer "Share a summary", which produces a composed, redactable document whose
    contents the reader chooses.
  </DoDont.Do>

  <DoDont.Dont>
    Block screenshots. The reader photographs the screen with another phone, and
    now there is an unencrypted image with no redaction in someone's camera roll.
  </DoDont.Dont>
</DoDont>

## Components that implement this [#components-that-implement-this]

{/* Generated from `implements`. Do not restate the list by hand. */}

`Value` is specified to support a masked state that reserves identical layout,
because rules 2 and 3 are only reliable if the masking happens at the same place
the formatting does. Surfaces that carry sensitive categories are specified to
take a sensitivity flag rather than each screen inventing its own treatment. Both
of those remain specifications rather than shipped behaviour. Nothing that ships
today has a masking, hiding or sensitivity prop of any kind.
`MetricTile` says so in its own file, and the others have no such affordance
either. Every rule on this page is therefore the product's to keep in its own
code, and a screen that keeps none of them will look exactly like a screen that
keeps all of them.

## What this does not cover [#what-this-does-not-cover]

* **Authentication, session management and device security**, which are your
  application's concern.
* **Encryption at rest or in transit**, and everything else that happens off the
  screen.
* **Screen recording and remote access**, including accessibility services with
  broad permissions on the device.
* **Shared devices and household use**, where the threat model changes
  substantially and account separation matters more than masking.
* **Physical privacy filters**, which are a hardware answer to the same problem.
* **What may appear in a notification**, which is
  [Notifications and off-screen alerts](./notifications-and-off-screen-alerts.mdx).

<Todo>
  Specify the masked state on 

  `Value`

   and the sensitivity flag as real props,
  including what a mask renders as at every size and what the reveal control is
  called for a screen reader. Until they exist, rules 2, 3 and 4 cannot be met by
  using an opsinjs component and have to be built again on each screen.
</Todo>

## Updates to this page [#updates-to-this-page]

<Reviewed />
