Components
Buttons
Rectangular with a 2 px corner, mono uppercase, arrow on the right. The arrow is not decoration — it says the action leads somewhere, and it moves 4 px in that direction on hover. On the black button the label is not white but the foil, clipped into the letters.
Variants
Primary
The lime gradient — the light layer. Exactly one primary button per screen.
Hover and press are light moving, not a change of colour. The plate
keeps --gradient-light-90 in every state; what responds is a soft
specular travelling in from off the right edge — the same gesture
Glass makes, driven by the same registered
--cf-specular. Press is the one place the contour comes up:
.cf-btn already reserves a 1 px transparent border, so inking it
costs no layout and says pressed in the brand's own primitive rather than in
another colour, while the specular eases back under the finger.
Because light only ever adds, the darkest point on the plate never moves: the black label measures 13.45:1 in all three states. The lime edge is untouched by hover, so the screen's one lime moment is not spent on a pointer.
<button class="cf-btn cf-btn--primary"> Kontakt aufnehmen <svg class="cf-arrow" aria-hidden="true"><use href="#cf-arrow"></use></svg> </button>
Glass
For the hero: the same gradient, but the plate underneath it is frosted so the button holds on moving artwork. Hover it — a soft band of light travels in from off the right edge and settles across the plate. Press it and the band eases back out, because the surface tilts away from the source under the finger. Behind the label light only ever adds, so the darkest point the label sits on stays the bearing tint and the contrast floor does not move in any of the three states.
The press is the one place this plate and Primary differ,
and the difference is the material rather than an inconsistency. Primary inks its
reserved 1 px border to say pressed; this plate's border is already a lit
rim — white 55 % from --glass-border, with
--glass-edge painted on the same pixel underneath it — so inking it would
trade the glass edge for a black line drawn over the hero video. On glass the specular
moving is the whole signal, which is the reason the material has one.
That edge is the token the navigation sheet ends at, not a copy of it: both frosted surfaces in the system now finish on one declaration. It is windowed to the left 62 % of the plate so its Sky tail expires just before the face gradient's lime begins at about 63 % — a plate with lime at one end cannot carry the material's chroma at that end as well. → Materials
That edge is also the only part of this button a reader without a pointer ever
sees move. All three states above are :hover,
:focus-visible and :active, and a touch device sends none of
them — so on a phone the site's primary call to action was a still plate. The rim now
carries --glass-rim-light too: the navigation sheet's own band, at the same
width and the same two parking positions, crossing once as the button travels up the
screen. It rides the rim's 62 % window rather than the plate, so it dies where
--glass-edge does and never reaches the lime; it is painted on an unblurred
pseudo-element, so the plate's three background layers and every contrast figure
measured on them are untouched; and it is parked off-canvas as a plain declaration, so
reduced motion, print and a browser without view timelines all render the plate exactly
as it is drawn today.
The demo sits on the hero poster, not on a flat fill. Frosted glass over a flat surface has nothing to calm and shows nothing — a demo on a plain gradient would be documenting a grey box rather than the material.
Outline
Secondary actions. The 1 px contour is the brand's default frame.
Solid — the black button
On light, busy surfaces where a contour would disappear. Its label is not white: the foil is clipped into the letterforms, so light enters at the first character and arrives, coloured, at the arrow.
Enlarged, so the travel is visible. This is the same button at 3×, not a separate style — the 2 px corner is drawn at 6 px here for the same reason:
It needs no extra class and no label span — .cf-btn--solid is the whole
API. Every black button on the site gets this, which is the point: it is what a black
button is, not a decoration applied to some of them.
Where it comes from
The manual's app icon (Farben > Anwendung) is a black plate with the mark filled by the light ramp — the plate stays black and the mark carries the colour. A black button is the same object at UI scale, so the label carries the colour.
It is the foil and not --gradient-light, and that choice is
load-bearing. The light ramp starts at lime, and lime is a moment — one element per
screen. There can be several black buttons on a page, so a lime-tipped label would
spend the screen's one lime moment on a secondary download link. The foil is the cool
half of the same spectrum with the lime cut off, which is exactly why it exists.
→ Colour
The gradient box is the whole button, not the label. At 116.57° across a 352 × 48 slab the ramp is 336 px long and runs left to right, so the label — which sits in the left half — gets the Weiss → Glas → Sky leg, and the arrow at the far right gets Violett. Scoped to a label span instead, every button would ramp the full spectrum inside ten characters and read as a rainbow rather than as light crossing a plate.
Hover lifts, it does not deepen. The plate is --surface-inverse,
which is black, and there is nothing under black to go to — so hover raises it to
grey-800, the ramp's darkest non-black step and the same value
--surface-raised takes on an inverse surface. The button rises the
distance a raised panel does, and it does not stop being a black button. It used to
work the other way round, resting on anthracite and deepening to black: a state change
of 1.2:1, which is to say almost none.
| Foil stop | Hex | On black | On grey-800 (hover) |
|---|---|---|---|
| Weiss | #FFFFFF | 21.00:1 | 16.48:1 |
| Glas | #C5EBE2 | 16.36:1 | 12.84:1 |
| Sky 300 | #B8D7F1 | 14.03:1 | 11.01:1 |
| Violett 300 | #BEBFF0 | 11.90:1 | 9.34:1 — the floor |
Against a 4.5:1 requirement there is no size at which this label is the weak part of
the page, which is why it can be the default here rather than an opt-in for large type.
Browsers without background-clip: text, print and Windows high-contrast
all fall back to a solid label on a solid plate — the state the button had before.
Ghost
An underlined inline link. For tertiary actions close to body copy. The one button that stays square: it has no plate, only a 1 px rule, and a rule is structure.
On dark
Anatomy
| Property | Value |
|---|---|
| Minimum height | 48 px (3 rem) — touch target |
| Padding | --space-3 / --space-4 |
| Label ↔ arrow | --space-6, pushed apart with space-between |
| Type | Geist Mono Medium, 12 px, +0.08em, uppercase |
| Corners | 2 px · --radius-sm — rx="2" on all three CTAs in the export |
| Label | --text-inverse, except on --solid, where it is the foil |
| Arrow | 1.25em, currentColor, +4 px on hover and on :focus-visible — --violett-300 on --solid |
| Hover / focus | specular travels over --duration-slow — light does not snap. 64 % on --primary, 40 % on --glass. --outline inverts to the black plate and --solid lifts to --grey-800, on both states alike |
| Press | specular eases back under the finger — 92 % on --primary, 76 % on --glass. The contour inks to --accent-ink on --primary only: --glass already carries a lit rim there |
Edge on --glass | one line — the lit rim, --glass-border. Not a state; it is the material. Glass is never outlined in black, so the plate makes no measured boundary claim against the artwork behind it. See Materialien |
Scroll on --glass | --glass-rim-light crosses the rim's 62 % window once as the plate travels up the screen — the navigation sheet's band, on a view() clock. Not a state either: it is the one response that reaches a reader with no pointer |
Buttons are set wide rather than shrunk to their content: label left, arrow at the
right edge. That is the shape from the design. In narrow contexts use
.cf-btn--block for full width.
The arrow sprite
In this documentation the sprite is injected by JavaScript. On the website it belongs
directly after <body> in the page template — once per page:
<svg aria-hidden="true" style="display:none"> <symbol id="cf-arrow" viewBox="0 0 600 480"> <g transform="translate(600,0) scale(-1,1)"> <!-- paths from assets/img/icons/cf-arrow.svg --> </g> </symbol> </svg>
Rules
Do
- One primary per screen; everything else outline, solid or ghost.
- Use verbs in the label: “Kontakt aufnehmen”, not “Kontakt”.
<a>for navigation,<button>for actions.- Let the black button's foil label be — it is the variant, not an option on it.
- Disabled states via the
disabledattribute plus a--grey-200surface.
Don't
- No icons other than the CF arrow.
- No corner other than
--radius-sm, and no shadows. Never a pill. - No primary button on a lime surface.
- No lime in a button label — that is what the foil is for.
- No lowercase labels — the mono uppercase is part of the brand.