---
title: "Diabetes medicines app"
description: "A whole single-page app assembled only from opsinjs parts. It records the medicines somebody takes, reminds them, and refuses to do anything else."
url: "https://opsinjs.pensievelabs.org/screens/diabetes-medicines-app"
source: "https://opsinjs.pensievelabs.org/screens/diabetes-medicines-app.md"
section: "Screens"
kind: "screen"
evidence: "mixed"
reviewed: "2026-09-20"
reviewer: "clinical"
aliases: ["medicines app", "dose reminder", "medication tracker", "medicines diary", "dose diary"]
implements: ["accordion", "badge", "button", "callout", "card", "care-card", "checkbox", "combobox", "consent-sheet", "dialog", "disclaimer-note", "divider", "dose-tracker", "empty-state", "field", "icon-button", "link", "log-sheet", "menu", "number-field", "radio-group", "relative-time", "segmented-control", "select", "sheet", "source-citation", "stepper", "surface", "switch", "tab-bar", "term", "textarea", "timeline-entry", "value", "visually-hidden"]
implemented: true
---

> 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]

Somebody takes four medicines for their diabetes. Two are tablets, one is a
weekly injection and one is a pen they use at bedtime. They open this app to
answer three questions, in this order: **what am I meant to take today**, **what
have I already taken**, and **what did I tell this app over the last week**. It
answers those three, it reminds them at times they chose themselves, and it hands
the record back in a form they can take to an appointment.

It is the first specimen on this site that is an application rather than one
screen. The [results screen](./results-screen.mdx) proves that seven parts
compose. This one asks a harder question: whether a product can be built out of
the kit, with four destinations, five overlays, a form, a consent decision and a
history, and still hold the rules the rest of this section argues for. Thirty-five
components meet here, which is over half the roster.

**What it deliberately does not do, and this is the design rather than a gap.**
It calculates no dose. It changes no dose. It says nothing about what to do about
a dose somebody did not take. It checks nothing against anything: no
interactions, no duplicate medicines, no glucose, no thresholds of its own. It
marks nothing as missed on its own authority, because it cannot know. And it
stores nothing: there is no account, no sign-in, no server and no network call,
which is the honest answer to "where do my medicines go" and is also what keeps
the product out of a regime it could not satisfy.

<SafetyCallout severity="attention" evidence="cited">
  Those refusals are not politeness. The MHRA's guidance on stand-alone software
  names an app that works out an insulin dose from the carbohydrate in a meal as
  a worked example of a medical device, and names a reminder, a diary that
  replaces a written one, and referral to a clinician as the things that keep
  software outside the definition. The same guidance states that a general
  disclaimer does not exempt an app that qualifies as a device. So the refusals
  are in the code, and the copy only reports them. Read the guidance in its
  current published form with your own regulatory advisers; this page is design
  rationale, not a compliance assessment. [Regulatory
  context](../health/regulatory-context.mdx) is the map.
</SafetyCallout>

**Who this specimen is for.** A team building a medicines surface, who need to see
what the boundary looks like when it is drawn in components rather than in prose.
And a reviewer, who can hold the rendered app against the three screen-level checks
in [Screens](./index.mdx) without taking anything on faith.

## Composition [#composition]

<CompositionTree name="diabetes-medicines-app" />

One file, one default export, and four destinations inside it. Rungs are given by
position rather than by name so this page cannot drift from
[The ladder](../foundations/materials/the-ladder.mdx).

```text
DiabetesMedicinesApp                      page ground
├── TermGlossaryProvider                  two words, expanded in place
├── Header                                page ground
│   ├── Title and date                    one H1 for the whole application
│   ├── Badge                             the demonstration marker, on the surface
│   └── IconButton                        opens the about sheet
├── Main                                  one of four, focus target on change
│   ├── Today
│   │   ├── CareCard                      the one dose nobody has answered for
│   │   ├── Callout                       what the app will not say about it
│   │   ├── Card                          the confirmation, with Undo beside it
│   │   ├── DoseTracker                   what was recorded, and only that
│   │   ├── Card + Badge                  what is still to come, which is a plan
│   │   ├── Button                        opens the log sheet
│   │   └── SourceCitation                where every figure on the screen came from
│   ├── Medicines
│   │   ├── Callout                       the record against the prescription
│   │   ├── CareCard                      reorder, at the count the person chose
│   │   ├── Card + Badge + Menu           one per medicine
│   │   ├── Card + Stepper + Combobox     add a medicine, four steps, on the page
│   │   └── Accordion                     stopped medicines, kept with their dates
│   ├── Record
│   │   ├── SegmentedControl              how far back to read
│   │   ├── Card + Value                  the count, never a grade
│   │   ├── Divider + TimelineEntry       one list per day
│   │   └── Card + Field + Textarea       the summary, ready to copy
│   └── Reminders
│       ├── Callout                       what a browser reminder cannot do
│       ├── Card + Switch                 on, and per medicine
│       ├── Card + Select + RadioGroup    when, and what may reach a lock screen
│       └── Card + Checkbox + NumberField reorder, at the person's own number
├── DisclaimerNote                        page ground, lowest emphasis, every section
├── TabBar                                sticky, four destinations
└── Overlays
    ├── LogSheet + RadioGroup             record a dose, with no popup inside it
    ├── Sheet                             about this demonstration
    ├── ConsentSheet                      turning reminders on
    └── Dialog                            confirming a medicine has been stopped
```

Four composition rules this app exists to demonstrate.

**A log and a plan are different objects, and only one of them is a log.**
[DoseTracker](../components/dose-tracker.mdx) holds what was recorded and nothing
else. A dose that has not happened has no recording instant, and the day's
remaining doses are drawn beside the log as plain cards. The reason is visible in
the console: DoseTracker forwards every instant to
[RelativeTime](../components/relative-time.mdx), which treats an instant later
than `now` as two clocks disagreeing and says so, and which prefixes every entry
with the word "Recorded". Both are correct for a log and both are wrong for a dose
still to come.

**The card is not the only surface off the page ground, because an app is not one
screen.** Four destinations each lift their own groups, and the sticky bar sits on
the translucent rung above all of them. What is preserved is the relative claim: a
card is one rung above the section it sits in, and nothing on a section competes
with the thing that section exists to answer.

**Every overlay is somewhere the reader chose to go.** Nothing opens on arrival.
No dialog, no permission prompt, no toast and no interstitial fires without a
press, which is what makes the [consent sheet](../components/consent-sheet.mdx) an
honest question rather than a gate.

**No popup control sits inside a sheet, and that rule was learned rather than
chosen.** A Select inside the log sheet and a Combobox inside an add sheet both
render their list and both are unpressable: the popup and the sheet's own surface
sit at the same stacking level, the sheet comes later in the document, so it paints
over the open list and a press lands on the sheet behind it. Measured in the
browser, not argued from the source. So the log sheet asks which medicine with a
radio group, which is the better control for four items on a phone anyway, and
adding a medicine is a task on the page rather than a sheet over it, which is what
the [medications pattern](../patterns/ask-users-for/medications.mdx) asks for in any
case. The workaround is good design either way. The defect is real and is recorded
below.

**The kit is composed, never re-skinned.** Where a part did not exist the product
wrote plain markup out of the same tokens, and said so in a comment: the day's
plan, the visible labels on the two controls that ship none, and the polite live
region on the confirmation.

## Preview [#preview]

<IframePreview kind="screen" name="diabetes-medicines-app" device="phone" height="720">
  The whole app, rendered under the opsinjs product theme. The four destinations
  at the foot are live, the log sheet saves, and the undo beside a saved record
  puts it back. Every medicine, dose, time and count in it is invented.
</IframePreview>

Open the frame at tablet and desktop width and the app stays one column, because
the reading order is the same order at every width and a second column would put
the thing somebody came for beside something they did not. Switch the frame to
dark and every state survives, because no state here is carried by a colour.

**Everything in it is synthetic (ADR 0012).** The medicines are called "Morning
tablet" and "Bedtime pen", never a real drug and never a real strength. The
instants are fixed, so the app says the same thing every time it is built. A
screenshot of this page must never be mistakable for somebody's own medicines
record, which is why the demonstration marker sits on the surface rather than in
an about screen nobody opens.

## Safety notes [#safety-notes]

**It carries neither colour axis, and that is the finding.** No surface in the app
takes `data-status` or `data-category`, and none is tinted from either ramp. A
medicines record states no clinical level and names no category, so painting one
would be a verdict the product has no right to. A dose with nothing recorded
against it is never painted the red that means "act now", because whether it
matters is a clinical judgement this product does not own, and a red that means
"you have not logged this" cannot share a screen with a red that means "contact
your care team". [The two colour axes](../health/two-colour-axes.mdx) is the rule;
this screen is what obeying it all the way looks like.

**The app never marks a dose as missed.** It cannot know. The `missed` state
reaches DoseTracker only where the person themselves pressed a control that says
"I did not take it", and a dose nobody has answered for stays at `scheduled`
however long ago it was due. No clock moves it. This is the one place a medicines
app is most likely to assert something it has not got, and it is the reason
DoseTracker's own specification insists it reaches no verdict about any entry it
draws.

**A dose that was not taken routes to a person, and stops.** The app does not say
"do not double up", it does not say "take it as soon as you remember", and it does
not say anything conditioned on which medicine it was or how late it is. The
answer genuinely differs by medicine and by person, and a generic answer attached
to a specific medicine is treatment advice however it is worded. What the screen
does is name who can answer: the pharmacist, the prescriber, and the leaflet in the
box.

**No number in the app belongs to opsinjs, and none belongs to the product
either.** The doses are strings the person transcribed from their own label, shown
back unchanged rather than parsed into a quantity, because a dose is a written
instruction and not a measurement. The count at which a reorder reminder appears is
a number the person set. The emergency routing is the product's own copy, carried
here precisely because opsinjs ships no emergency number, in the same breath as it
ships no reference range: it does not know where the reader is.

**The emergency routing is never triggered.** It sits in the disclaimer at the foot
of every destination and it fires on nothing. A modal that opens because a value
crossed a line is the product making a triage judgement, and
[Emergency and escalation](../health/emergency-and-escalation.mdx) is where that
path is specified for a product that owns one.

**A dose is written back, never parsed.** The add flow offers names and forms and
nothing else. There is no strength picker, no prefilled dose and no dose-shaped
placeholder, because a list of strengths to choose from is a list of doses and a
default is a recommendation. The word "units" is written out in full everywhere it
appears, never shortened to a single letter after a number.

<ResearchNote evidence="mixed" date="2026-09-20">
  **Cited.** Huckvale K, Adomaviciute S, Prieto JT, Leow MKS, Car J. Smartphone
  apps for calculating insulin dose: a systematic assessment. *BMC Medicine*
  2015;13:106. Of 46 insulin dose calculator apps assessed, 91 per cent lacked
  numeric input validation and 67 per cent carried a risk of an inappropriate
  output dose, while 59 per cent carried a clinical disclaimer. The disclaimer was
  shipped by more apps than input validation was. That is the evidence behind this
  screen treating a disclaimer as a required disclosure and never as a mitigation.

  **Cited.** The FDA's Class I recall of the Insulet Omnipod 5 Android App,
  initiated 7 November 2023, records that a bolus entered without a leading zero
  could be read as ten to one hundred times the intended amount, because the
  calculator did not register a decimal point entered as the first character. A
  regulated device from a major manufacturer, and the near-fatal defect was a text
  field. It is the reason this screen ships no numeric dose field at all.

  **Opinion.** That a medicines record belongs on neither colour axis is ours, and
  it is the single decision on this page most worth arguing with. If it is right,
  the design system has a gap where a non-clinical state vocabulary should be. If
  it is wrong, this screen is under-describing something a reader needs.

  **Opinion.** That a count is safe and a grade is not. "You recorded 18 of 22" is
  a fact about a log; a percentage under a label is an interpretation of a person.
  The line between them has not been tested with readers.

  What would change our mind: a clinical safety officer's review of this screen
  against a real hazard log, and a usability study with the audience it is drawn
  for, would revise both opinions and the strength they are stated at.
</ResearchNote>

## Accessibility [#accessibility]

**Landmarks and headings.** One `header`, one `main`, one `footer` and the `nav`
that TabBar supplies. One H1 names the application, one H2 names the destination
showing, and H3s name the groups inside it. The heading outline read on its own is
a usable summary of whichever destination is open.

**Focus follows the destination.** The bar swaps the whole of `main`, so `main`
carries `tabIndex={-1}` and takes focus when a destination changes. Without that, a
screen-reader user and a keyboard user are left at the foot of a page that is no
longer the page they were on. The focus ring is never suppressed, and the sticky
bar is the one thing on the screen that can cover a focused control at the end of a
long list. [Keyboard and focus](../accessibility/keyboard-and-focus.mdx) sets the
rule.

**The confirmation is announced, and it does not leave.** A saved record puts a
persistent panel on the surface carrying what was recorded and the undo beside it,
in a `role="status"` region. There is no toast anywhere in this app, and that is
deliberate: a message that dismisses itself after a few seconds is unreadable at
the reading speed this audience reads at, and it takes the undo with it when it
goes. [Timing adjustable](../accessibility/cognitive-accessibility.mdx) is the
constraint; nothing here expires, and no dose changes state while somebody is
reading it.

**Colour independence.** Nothing on the screen is told by colour. Every dose state
carries a word and a distinct lucide silhouette, every control carries its label,
and the whole app reads the same in greyscale and under all three simulated
colour-vision deficiencies. [Colour independence](../accessibility/colour-independence.mdx)
is the rule, and this screen is the easy case for it because neither clinical
palette is in use.

**Targets and text.** Every control floors at the published target size in rem, so
it grows with the reader's own text size rather than staying put. Type comes from
the opsinjs steps and never from a pixel value, so a reader at 200 per cent gets a
larger reading of their own medicines. Nothing in the content truncates: a medicine
name wraps, and the dose beside it wraps with it.
[Target size and motor](../accessibility/target-size-and-motor.mdx) and
[Text resizing and zoom](../accessibility/text-resizing-and-zoom.mdx) set both
floors.

**One thing does not reflow, and it is measured rather than argued.** At 200 per
cent text in a 390 pixel frame the document scrolls sideways by 115 pixels, and
every element past the edge belongs to the tab bar. Four destinations, each floored
at the target size in rem and each carrying a word, come to more than the width
they have. Nothing the app owns overflows at that setting, and the app cannot
repair the bar from outside it. The question is recorded below rather than worked
around, because a workaround here would hide a defect in a component other products
will meet.

**Two controls ship no visible label, so the product draws one.** Select and
NumberField apply their `label` as an accessible name and expose no id, so the app
renders the same words above each one as a plain span. A `label` element with
nothing to point at is markup that looks associated and is not, which is why it is
a span, and the visible words match the accessible name so voice control reaches
the control by what a person can see.

**Reading order matches visual order** on every destination: what needs an answer
first, then what was recorded, then what is still to come, then the way to add
something, then where it all came from, then the boundary. The boundary sits in the
same place on all four, so it is findable rather than merely present.

## Status [#status]

<StubNotice
  name="diabetes-medicines-app"
  status="shipped"
  questions="[
  &#x22;DoseTracker names this exact job, and two of its four marker words are words a patient-facing medicines record would rather not write. Should the four words be props, or should there be a fifth state meaning nothing was written down?&#x22;,
  &#x22;Select renders the raw value in its trigger until the popup has been opened once, so an id-shaped value reads as an id on first paint. This screen works round it by making the value and the label the same words. Where does the repair belong?&#x22;,
  &#x22;LogSheet reads the real clock itself, so a specimen pinned to a fixed instant has to re-stamp every entry it saves. Every other health component takes a `now`. Should this one?&#x22;,
  'TabBar stamps aria-current=&#x22;page&#x22; on its current item. This bar swaps a region inside one document, where the correct value is &#x22;location&#x22;. The component hard-codes it.',
  &#x22;TimelineEntry takes no `locale`, so the date it formats follows whatever locale the runtime resolves. This screen carries the load-bearing date in each entry's own title to work round it.&#x22;,
  &#x22;opsinjs ships no announcer, so this screen builds its own polite live region. A system that rules a toast out on a health surface probably owes its products the replacement.&#x22;,
  &#x22;Is a count already an interpretation? This screen states how many doses were recorded out of how many had times set, and never a percentage or a verdict. That line has not been tested with readers.&#x22;,
  &#x22;Is a visible demonstration marker enough? A working medicines app on a design system site can be mistaken for a product by somebody skimming, and a specimen that refused to accept typing would mislead nobody and demonstrate far less.&#x22;,
  &#x22;A popup control rendered inside a Sheet cannot be pressed. Select inside LogSheet and Combobox inside Sheet both open their list, and both put it behind the sheet: the popup and the sheet surface share a stacking level and the sheet is later in the document. Does the popup need to render above the sheet, or should a sheet refuse to host one?&#x22;,
  &#x22;TabBar does not reflow at 200 per cent text. Measured at 390 pixels with the text step at 200, four destinations push the document 115 pixels wider than the viewport, and every element past the edge is one of its own. Does the bar drop its labels at that size, scroll on its own axis, or take fewer destinations?&#x22;,
]"
>
  Everything on this page renders. The app is assembled entirely from parts that
  already ship, it holds its own state, and the controls work. What is unproven
  is the composition rather than the components: the four destinations, the
  refusals written into them and the accessibility contract above are a
  proposal, they have had no clinical safety review, no hazard log, no clinical
  safety case and no data protection impact assessment, and none of those is
  claimed. Every opsinjs component has now been audited against WCAG 2.2 AA by
  its authors, in a static source pass and a rendered pass, but that audit is
  author-run rather than an independent accessibility review, and no component
  has had a clinical review either
  ([ADR 0025](../project/decisions/0025-the-audit-is-author-run.mdx)). Do not
  ship this app to readers. Do generate product code against its
  refusals, because those are the part worth copying.
</StubNotice>
