Foundations · 14
The Found State
The brand is named after a keyboard shortcut, and the system had no drawing for what that shortcut returns. A match is one object with two rungs: a word standing on a lattice edge, and — for the one match the reader is standing on — the same word with the light layer switched on behind it.
Where this comes from
No plate in assets/source/manual/ draws a highlight, and neither mockup
carries one, so this chapter is derived from the language the way
Plot and Table
are: built only out of parts the manual does establish — the hairline, the light layer,
the lattice cell, the presence ladder. What makes it worth deriving is the name over the
door. Control-F is find-in-page. A brand whose whole argument
is that data can be found in a space rather than read off a sheet cannot leave the
drawing of a found thing to the browser's default yellow.
And that is what it had been leaving it to. The platform gives an author four registers for this is the thing you were looking for, and until this chapter one of them was styled:
| Register | Who causes it | Before |
|---|---|---|
::selection | the reader, dragging | lime fill, black ink — the one that was drawn |
<mark> | the server, rendering an answer | UA yellow |
::target-text | the URL, arriving with a text fragment | UA yellow |
::highlight() | the page, searching itself | nothing registered |
Scroll-to-text-fragment reached Baseline in March 2025, the CSS Custom Highlight API
in June 2025, and hidden="until-found" in December 2025. Finding used to
be something a browser did to a page; it is now something a page can take
part in. That is a capability the system was not exploiting, and it is the one
capability that belongs to this brand more than to any other.
What a highlight may be made of
This is the constraint that shaped the drawing, and it is worth stating before the drawing itself. A highlight moves — the reader drags, steps, scrolls — so the browser has to be able to repaint it without laying the page out again. Highlight pseudo-elements therefore take a closed property list, and everything that would change geometry is off it:
| Property | Applies | What that costs this brand |
|---|---|---|
color | yes | — |
background-color | yes | one flat layer, behind the glyphs |
text-decoration-* | yes | the only line a highlight can draw |
text-shadow | yes | not used — a shadow is not one of the six materials |
-webkit-text-stroke-* | yes | not used — it thickens letterforms, it does not contour a box |
border, outline | no | a match cannot have a box |
padding, margin | no | a match cannot have air inside it |
background-image | no | no foil, no light ramp, no gradient at all |
That last row is the interesting one. background-image is ignored on a
highlight pseudo-element, which makes the found state the one surface in this
system that may not carry a gradient — no --gradient-foil, no
--gradient-light, no member of the family. Every other lit thing here is a
ramp. This one has to be made of a flat light and a line.
Contour before fill, always. Told it may have exactly one line and exactly one flat colour, this system draws the line first and treats the colour as light — which is what it says on every other page. The constraint did not bend the language; it selected for it.
Wherever lime fills an area in this system it fills it with a gradient —
a rake, the bloom, the foil. A plane painted #E1FF00 edge to edge is
not the light layer, because light here is a source and a falloff and a plane has
neither. The found state is flat lime, and the reason is the row above:
background-image is ignored on a highlight pseudo-element, so there is
no gradient form of the declaration to reach for. That rule listed
::selection; the exception is really every highlight register, for
one reason, and there is no fourth.
.cf-mark--current is the interesting case, because it is an element
and could take a ramp. It does not, for the same reason it takes no border:
a match must be one drawing whether the server marked it, a script registered it or
the URL arrived carrying it. The element is held to the pseudo-element's means.
Two rungs
Find-in-page has drawn two states since the day it existed — every match, and the one you are standing on — and no system here had named the difference. It is the presence ladder, read as attention rather than as arrival: same object, one rung apart, and an object may move along the ladder but never across it.
Every match is the ground line alone: one 2 px rule in ink, the lattice edge the word stands on. Present, but not where the reader is. The one match the reader is standing on is the same word with the light on — the fill is the light layer, in its correct place under the text and above the contour.
<mark class="cf-mark">ground line</mark> <mark class="cf-mark--current">the same word with the light on</mark>
The rule is a ground line and not a box, and that was a decision rather
than a shortcut. Two rules — underline and overline — are the obvious answer: the top and
bottom edge of a cell, the only box a highlight is allowed to have. It was built that way
first and then looked at. There is no text-overline-offset in CSS: the
underline can be pushed clear of the descenders and the overline cannot be pulled down at
all, so it lands on the font's ascent, well above cap height, and reads as a detached
rule floating over the word instead of its opposite edge. An asymmetric band is worse
than no band. One line at twice the weight draws what it means — and it is what the rest
of this system already does, because an isometric object has no bounding box either. It
has a lattice edge under it.
2 px, not the 1 px hairline, and that is a collision rather than a preference: an unclassed link in this system is a 1 px underline at 0.2em, one notch inside the mark's own 0.22em. At the hairline weight a match and a link would be the same drawing. Both weights are sanctioned screen strokes, so the mark takes the second one.
One lime moment per screen is a rule about composition — what the designer puts on the page. A search result set is not composed, it is answered, and if every match were lit a busy page would spend its whole light budget on the reader's query. It does not have to: at most one match is ever lit, because only one can be current. Eleven contours and one light. The budget is satisfied by the drawing rather than waived for it.
.cf-mark--current to be true of and lighting the top
result is a page pretending to have decided. A result set is drawn entirely in contour
and the light is deferred — each result link carries a
#:~:text= fragment, and the phrase lights up in the rung below when the
reader arrives on the page that holds it. Contour here, light there.
→ Search & Results
On a dark surface the rule inverts and the light does not, because the light is
the light — there is nothing for lime to become on black. --found-rule
resolves through --border-strong, so it follows the inverse theme without
the found state knowing the theme exists.
A match in the footer is drawn in light instead of ink, and the current one is the same lime with the same black ink on it.
<mark> is an element, so .cf-mark could have a
real 1 px border, 2 px of padding and a --radius-sm corner like every other
object in the system. ::target-text cannot, ever. Two drawings for one
meaning is worse than one constrained drawing, so the element is held to the
pseudo-element's means — a match looks the same whether the server marked it, a
script registered it, or the URL arrived carrying it.
A page that searches itself
The Custom Highlight API is the register that had nothing registered against it. It
takes Range objects and paints them through
::highlight(name) without touching the DOM — no wrapper elements,
no re-parse, nothing for a screen reader to trip over. The system ships the two names
and their styling; a page supplies the ranges.
Everything in this brand is drawn with a contour rather than filled. A contour is one
device pixel wide at every size, which is why an isometric contour is stroked with
vector-effect: non-scaling-stroke instead of being scaled with the
drawing it belongs to.
Anything spatial is constructed in 2:1 isometry, so a circle is an ellipse at 2:1 and a cube is three rhombi sharing a vertex. The only sanctioned angles are 26.57, 45, 63.43 and 90 degrees, and a rotation of a sanctioned angle is sanctioned.
Lime is light, not a surface. It appears once per screen: the lit contour, the plot cap, the focus ring, the banner edge — and, here, the one match the reader is standing on. Every other match on this page is a contour with no light in it at all.
// The whole feature. The styling is already in base.css. const found = new Highlight(); const current = new Highlight(); current.priority = 1; // the lit rung wins the overlap CSS.highlights.set('cf-found', found); CSS.highlights.set('cf-found-current', current); // …for each hit, a Range over the text node, then: found.add(range); current.clear(); current.add(ranges[i]);
It is the obvious joke for a company called Control-F and it is the wrong thing to build. The browser's own find is faster, works before any script has run, reads the whole page rather than one container, is wired into assistive technology, and is the one keystroke a reader can rely on across every site they will ever visit. A site search field is an addition to it — a control the reader chooses, drawn in the system's own field and icon set. Taking the keystroke away to show off the brand would spend the reader's trust on a pun.
Arriving on a match
A URL can carry the phrase it means rather than an anchor somebody remembered to add:
#:~:text=…. Search engines mint these, readers share them from the browser's
own Copy link to highlight, and the destination page implements nothing at all —
it only has to have drawn the state, which is what ::target-text
is. This is the register with the widest reach and the least code.
The article scrolls to Bevor irgendetwas simuliert wird and lights that clause in the found state — the lit rung, because a reader who arrives on a phrase is standing on it by definition. Nothing on that page knows this happened.
Clicking one from the page it points into does nothing in most browsers, which is why the demo above points at another page rather than at a paragraph below it. It also means the fragment travels through the system's page transition like any other navigation: the raked edge crosses, the article arrives behind it, and the lit clause is already lit when it gets there.
Findable while hidden
The last piece is the one that is not a drawing. Content collapsed with
display: none is invisible to find-in-page, to a text fragment and to a
search engine — so a reader searching for a word that is genuinely on the page is told
it is not. hidden="until-found" is the fix: the browser applies
content-visibility: hidden instead, keeps the text searchable, and reveals
the block when a match lands inside it.
Press Ctrl+F — Cmd+F on a Mac — and search for Rhombendodekaeder. The word is not on screen. Your browser will find it anyway and open the block it sits in.
Rhombendodekaeder. Twelve rhombic faces, every one of them the 2:1 cell this brand builds with, which is the only reason it is the word hidden here. Nothing revealed this block but the search itself.
<div hidden="until-found" id="answer-7"> <!-- no border, no padding, no background on THIS element --> <p>…</p> </div>
Do
- Pair it with a visible control. Content that only find-in-page can reach is content most readers cannot.
- Keep the element carrying the attribute bare — a hidden box still lays out, so a border or a padding on it draws an empty sliver where the content will appear. Style a child.
- Reach for
<details>first. Browsers already open a closed<details>on a find, so the accordion needs none of this.
Don't
- Never use it to hide content from sighted readers that assistive technology can still reach — it hides from both, which is the point.
.visually-hiddenis the other tool. - No animation on the reveal. The browser scrolls the reader onto a match; a block that then grows moves the match out from under them.
- Not for navigation. A collapsed menu holds links, not content, and a reader searching for a page name wants the page.
The one place the site collapses real prose is the accordion, which is
<details> and already findable. The attribute is documented here as
available material — the same standing the field and
the arrival ladder have — so that the first
thing which does need it gets it right instead of reaching for
display: none.
Measured
A custom highlight owes two figures, not one: 4.5:1 for the text against the highlight, and 3:1 for the highlight against the surface around it. The second is the one that decides this drawing.
| Pair | Ratio | |
|---|---|---|
| Schwarz on Lime — text on the lit rung | 18.51:1 | clears 4.5:1 |
| Lime against CF-Grau — fill against the top of the wash | 1.37:1 | fails 3:1 |
| Lime against Weiss — fill against the bottom of the wash | 1.13:1 | fails 3:1 |
| Schwarz against CF-Grau — the ground line, top of the wash | 13.48:1 | clears 3:1 |
| Schwarz against Weiss — the ground line, bottom of the wash | 21:1 | clears 3:1 |
Lime cannot hold the boundary at either end of the page — it sits at 87.5 % of white's luminance, which is what makes it light and what makes it useless as an edge. So the ink holds it and the light ranks it. The ground line is not decoration and cannot be dropped: remove it and the found state fails 1.4.11 on every surface the site has. It is also, in the same stroke, the answer to never carry a state by colour alone — the rules are what makes the mark, and the fill is what says which one is current.
| Path | What the reader gets |
|---|---|
| forced colours | Mark / MarkText, the system pair for exactly this meaning, with the rules kept |
| every match prints as the contour — there is no reader on paper to be standing on one, and lime prints as a wash over the ink | |
no ::highlight() | the page-search demo says so and the section stays findable with the browser's own find |
no ::target-text | the browser paints its own highlight; the reader still lands on the phrase |
no hidden="until-found" | the attribute falls back to plain hidden, so the block is collapsed and the visible control is the way in |
| no script | every register except the page-search demo is markup or CSS |
::selection keeps its solid lime and is the one exception on this
page.
Its boundary against CF-Grau is the same 1.37:1, and it is documented rather than fixed. A drag is transient, self-caused and under the reader's own hand — the reader knows what they just selected because they just selected it — and the criterion the fill actually has to clear there is the text on it, at 18.51:1. A selection that reads as anything other than a selection is a selection nobody can trust, so the platform's convention wins over the system's consistency. Every mark the reader did not make carries the contour.
background-clip: text is drawn with
-webkit-text-fill-color: transparent, and that property is
inherited — where it is set it beats the color of every
descendant. A match landing inside a .text-foil headline or a
.cf-btn--solid label therefore arrives already filled transparent.
Measured before this was fixed: .cf-mark--current rendered as an
empty lime block there — the plate, the ground line, and no word —
on both designed pages, at every size. A reader who searched for a term got a lime
slab where the term should be, which is the one failure this drawing exists to
prevent.
The three light rungs — .cf-mark--current, ::target-text
and ::highlight(cf-found-current) — now state
-webkit-text-fill-color: var(--found-ink) beside their
color, in forced-colors too, where the value has to be
MarkText rather than the CanvasText a foil hands its
descendants. ::selection states its own for the same reason.
The two contour rungs deliberately do not.
:where(mark)/.cf-mark takes color: inherit and
::highlight(cf-found) supplies no ink at all, because a match the reader
has not arrived at is the running text with a ground line under it — and inside a
foil, the running text is the foil. Inheriting the clip is the correct
drawing there rather than a bug it got away with. Only a rung that supplies its own
ink has an ink to lose. Held by scripts/check-highlight-fill.py.
Anatomy
| Selector | Rung | When |
|---|---|---|
<mark> | contour | the bare element, styled so an unclassed <mark> in prose is never UA yellow |
.cf-mark | contour | a match the server rendered. Every hit on a result page — see below |
.cf-mark--current | light | the one hit the reader is on. One per page, never two — and none at all where the reader has not chosen yet |
::target-text | light | automatic — a URL arrived carrying #:~:text= |
::highlight(cf-found) | contour | ranges a script registered under that name |
::highlight(cf-found-current) | light | the active range. Set priority above cf-found so the overlap resolves to the light |
::selection | light, no contour | the reader's own drag. Not part of the found set — see above |
<kbd> | — | a keycap: contour, 2 px corner, mono. Added here because the brand is a shortcut and could not draw a key |
| Token | Value | Job |
|---|---|---|
--found-rule | --border-strong | the ground line. Follows the inverse theme, so a match in the footer is drawn in light instead of ink |
--found-rule-weight | --stroke-2 | 2 px. The hairline is what an unclassed link already draws |
--found-offset | 0.22em | pushes the rule clear of the descenders. The extra notch over a link's 0.2em is what a 2 px rule needs to sit at the same optical distance |
--found-light | --cf-lime | material layer 5, flat, behind the glyphs |
--found-ink | --cf-schwarz | text on the light. Black in both themes, because the light is the same in both |
Rules
Do
- The ground line on every match. It is what holds the 3:1 boundary and what keeps the state off colour alone.
- Exactly one lit match per page. If two are lit, one of them is lying about where the reader is.
- Style a match through
--found-*, never with a fresh colour — the four registers must stay one drawing. - Give a page-search control a live count.
role="status"on the counter, and step buttons with real labels. - Move the reader to a match with
scrollIntoView, and honourprefers-reduced-motionwhen you do.
Don't
- Never intercept Ctrl+F. The browser's find belongs to the reader.
- No gradient in a match. It is not a taste call —
background-imageis ignored on a highlight pseudo-element, so the element version would be the only one that had it. - No border, padding or radius on
.cf-mark, even though an element could carry all three. - Do not wrap search hits in
<span>s a script injects. Use the highlight registry: it paints ranges without touching the DOM, so the text a screen reader reads is the text the author wrote. - Do not use
<mark>for emphasis. It means relevant to why you are here;<strong>and<em>mean the other things. - Never
display: noneon prose a reader might search for. That is whathidden="until-found"is for.