Files
op-packages/luci-theme-footstrap/styles/theme/55-buttons.css
T

305 lines
17 KiB
CSS

@layer theme {
/* Buttons placed straight in a field, not in a .control-group (app action rows
* Enable/Disable, Start/Restart/Stop): inline-block buttons only get whitespace spacing
* horizontally, so on wrap the rows collapsed together. Wrapping flex with a real gap. */
.cbi-value-field:has(> .cbi-button) {
display: inline-flex; flex-wrap: wrap; gap: var(--fs-space-2); align-items: center; justify-content: flex-end;
}
/* The button. The native `input` types are in the selector list on purpose: LuCI's own views
* always use <button class="cbi-button">, but a third-party luci-app-* may emit a bare
* <input type="submit">, and base only gives those width/height: auto — coverage is a
* contract (docs/gallery.html). `.btn` and `.cbi-button` are interchangeable in LuCI markup
* (a row-action button usually carries both), so every rule here names both. */
.cbi-button, .btn,
input[type="button"], input[type="submit"], input[type="reset"] {
display: inline-flex; align-items: center; justify-content: center;
/* auto, stated: theme/60-inputs.css's generic `input` box says 210px x 32px, and a base
* reset would lose to it by layer — a button sizes to its label */
width: auto; height: auto;
min-height: var(--fs-ctl-h);
padding: var(--fs-space-2) var(--fs-space-3-5);
font-family: var(--fs-font-sans); font-weight: var(--fs-weight); line-height: 1.1;
color: var(--fs-text);
background: var(--fs-panel2);
border: 1px solid var(--fs-border);
border-radius: var(--fs-radius);
box-shadow: none; text-shadow: none;
/* border-COLOR, not `border`: the shorthand animates border-width too — a layout
* animation on every hover instead of a paint one (absorbed from base with the ring) */
transition: border-color var(--fs-dur), box-shadow var(--fs-dur);
}
/* bare `button` rides along: a third-party app's unclassed <button> hovered the same border
* cue in base's old glow rule, and border-color is a no-op on its border: 0 box — harmless
* there, meaningful the moment the app gives it a border */
.cbi-button:hover, .btn:hover, button:hover,
input[type="button"]:hover, input[type="submit"]:hover, input[type="reset"]:hover { border-color: var(--fs-dim); }
/* Keyboard focus — not decoration: without this a button shows NOTHING on focus. The
* `box-shadow: none` above kills any stray focus glow (theme beats base regardless of
* specificity), and the UA outline only ever shows on :focus-visible, which this rule
* restyles — so Save, Save & Apply, Reset, every row action and the login submit would
* otherwise look untabbable. A plain mouse :focus deliberately shows nothing: base's old
* glow rang every clicked button, and the pressed cue below already answers the click.
* Same --fs-focus-ring as every field, so focus looks like one thing. */
.cbi-button:focus-visible, .btn:focus-visible,
input[type="button"]:focus-visible, input[type="submit"]:focus-visible, input[type="reset"]:focus-visible {
outline: none;
border-color: var(--fs-accent);
box-shadow: var(--fs-focus-ring);
}
/* Solid variants sit ON --fs-accent/--fs-good/--fs-danger, so an accent-tinted ring is
* invisible against them: ring in the surface colour with an accent halo. That halo was
* written out here as a literal ending in --fs-accent-soft — a 10-15% tint, i.e. 1.15-1.29:1
* against the surface, so the only thing this rule really painted was the 2px gap. It is
* --fs-focus-ring-solo now: same geometry, solid accent, and one name for the four rules that
* had been spelling it out. */
.cbi-button-positive:focus-visible, .cbi-button-action:focus-visible,
.cbi-button-negative:focus-visible, .cbi-button-remove:focus-visible,
.cbi-button-apply:focus-visible, .cbi-button-save:focus-visible,
.btn.primary:focus-visible, .btn.danger:focus-visible,
.btn.error:focus-visible, .btn.success:focus-visible, .btn.info:focus-visible {
outline: none;
border-color: transparent;
box-shadow: var(--fs-focus-ring-solo);
}
/* Pressed state. The `box-shadow: none` above would erase any press cue, so the inset shadow
* is declared here — the sole copy (base's identical one was absorbed). */
.cbi-button:active, .btn:active, .btn.active,
input[type="button"]:active, input[type="submit"]:active, input[type="reset"]:active {
box-shadow: inset 0 2px 4px color-mix(in srgb, var(--fs-border) 40%, transparent);
}
/* Solid variants — THREE fills, not six. `.cbi-button-*` is what LuCI's own views emit;
* `.btn.<colour-word>` is what a third-party luci-app-* tends to emit, and base/60-modal.css
* paints those words with gradients that are DEAD — the plain `.btn` rule above sets
* background/border/color in a LATER layer, which wins regardless of specificity, so an
* unclaimed `.btn.danger` would render as a neutral grey button with no colour cue.
* Coverage is a contract, so the theme claims them — in the SAME rule as their
* .cbi-button-* twin, because written apart (which they were, twenty lines away with a
* byte-identical body) a recolour lands on one half and not the other. */
.cbi-button-positive, .btn.success {
background: var(--fs-good); border-color: transparent; color: var(--fs-on-good);
}
.cbi-button-action, .cbi-button-action.important, .btn.primary, .btn.info {
background: var(--fs-accent); border-color: transparent; color: var(--fs-on-accent);
}
.cbi-button-negative, .cbi-button-negative.important, .cbi-button-remove,
.btn.danger, .btn.error {
background: var(--fs-danger); border-color: transparent; color: var(--fs-on-danger);
}
/* This hover must stay AHEAD of the page-action Save rule below: at equal specificity its
* `border-color: transparent` would erase Save's outline on hover. (.cbi-button-remove is
* deliberately absent — it never had the lift, and the row-action rules colour it their
* own way.) */
.cbi-button-positive:hover, .cbi-button-action:hover, .cbi-button-apply:hover,
.cbi-button-save:hover, .cbi-button-negative:hover,
.btn.success:hover, .btn.info:hover, .btn.danger:hover, .btn.error:hover {
filter: var(--fs-hover-lift); border-color: transparent;
}
.cbi-button-reset, .cbi-button-link { background: transparent; border-color: var(--fs-border); color: var(--fs-dim); }
/* Size words. The `.btn` rule above re-sets padding and radius from the token scale in a
* later layer, so a size word that carried only a font-size (as base's absorbed copy did)
* came out with default-button padding. Theme owns .btn.large/.small in full now, geometry
* on the radius scale rather than base's hardcoded 6px. */
.btn.large { font-size: var(--fs-type-lg); min-height: var(--fs-ctl-h-lg); padding: var(--fs-space-2) var(--fs-space-4); border-radius: var(--fs-radius); }
.btn.small { font-size: var(--fs-type-xs); min-height: var(--fs-ctl-h-sm); padding: var(--fs-space-1) var(--fs-space-2-5); border-radius: var(--fs-radius-sm); }
.cbi-section .table[id] .td .btn,
.cbi-section .table[id] .td .cbi-button { padding: var(--fs-space-1) var(--fs-space-2-5); font-size: var(--fs-type); }
.alert-message .btn, .alert-message .cbi-button {
border-radius: var(--fs-radius); padding: var(--fs-space-2) var(--fs-space-3); font-weight: var(--fs-weight);
background: var(--fs-panel2); border: 1px solid var(--fs-border); color: var(--fs-text);
}
.alert-message .btn:hover { border-color: var(--fs-accent); color: var(--fs-accent); }
/* Page action bar (.cbi-page-actions) — the Save/Apply footer. base renders a tall
* half-empty plate with bottom-only radius; make it a compact rounded action bar. */
.cbi-page-actions {
background: transparent;
border: 0;
border-radius: 0;
box-shadow: none;
padding: var(--fs-space-2) 0;
margin: 0 0 var(--fs-card-gap);
text-align: end;
display: flex;
align-items: center;
justify-content: flex-end;
gap: var(--fs-space-2);
}
.cbi-page-actions .cbi-button,
.cbi-page-actions .btn {
min-height: var(--fs-ctl-h);
padding: 0 var(--fs-space-5);
border-radius: var(--fs-radius);
font-weight: var(--fs-weight);
display: inline-flex; align-items: center; justify-content: center;
vertical-align: middle;
}
/* Reset takes the SAME STEP of the ladder as Save (-soft at rest, -fill on hover), so the two
* read as equally weighted secondary actions beside the solid Save & Apply — but off the
* DANGER role, not the accent one: Reset discards your unsaved changes. (An opaque
* --fs-panel2 was the first attempt and weighed MORE than Save's translucent 10-15% tint: a
* solid surface and a tint do not read as the same fill, whatever their lightness.)
* Label is --fs-text for the same reason as Save's: --fs-dim on --fs-danger-soft measures
* 4.49:1 on --fs-panel in footstrap dark, the worst of the six palette x mode blocks — under
* AA by a hundredth, which is still under it. The role lives in the fill and the border. */
.cbi-page-actions .cbi-button-reset {
background: var(--fs-danger-soft);
border: 1px solid var(--fs-danger-line);
color: var(--fs-text);
}
.cbi-page-actions .cbi-button-reset:hover {
background: var(--fs-danger-fill);
border-color: var(--fs-danger-line-hi);
}
/* THERE IS NO `.spinning` PADDING LADDER HERE ANY MORE, and it must not come back. It used to
* be seven selectors reserving room for the absolutely-positioned glyph base draws — each one
* climbing a class above whatever else padded that element. The ladder lost twice in the field
* (issues #15 and #22, the second to a 0,5,0 data-table rule), and every loss looks the same:
* the spinner draws on top of the label, silently. base/95-luci.css puts the glyph IN THE FLOW
* instead, so it reserves its own space and no host padding is involved. */
input[type="file"]::file-selector-button {
margin-inline-end: var(--fs-space-3); padding: var(--fs-space-2) var(--fs-space-3-5);
font-family: var(--fs-font-sans); font-size: var(--fs-type); font-weight: var(--fs-weight);
color: var(--fs-text); background: var(--fs-panel2);
border: 1px solid var(--fs-border); border-radius: var(--fs-radius-sm); cursor: pointer;
}
input[type="file"]::file-selector-button:hover { border-color: var(--fs-accent); color: var(--fs-accent); }
/* ==================================================================
* Row-action buttons (Edit / Delete / Start-Stop) in a table or a .cbi-section-actions
* container. Compact so tables stay dense. Neutral outline by default; the variants colour
* the ones that carry meaning (Edit = accent, Delete = danger). The neutral :hover comes
* FIRST on purpose — it used to sit last and greyed the coloured variants back out on hover,
* which only an !important on their colours hid. Equal specificity, so a variant hover must
* be written after it.
*
* WCAG 2.2 SC 2.5.8 (Target Size, AA): a pointer target must be >= 24x24 px, or spaced so
* 24px circles centred on adjacent targets do not overlap. At 28px tall with a 1px margin
* either side the circles DID overlap, so the exemption did not apply. 32px clears it.
* ================================================================== */
.td .cbi-button, .td .btn, .cbi-section-actions .cbi-button, .cbi-section-actions .btn,
.cbi-section-table .cbi-button {
min-height: var(--fs-ctl-h-sm); padding: 0 var(--fs-space-3); font-size: var(--fs-type);
}
.td .cbi-button, .cbi-section-actions .cbi-button, .cbi-section-table .cbi-button {
background: transparent; background-image: none;
border: 1px solid var(--fs-border); color: var(--fs-dim);
}
.td .cbi-button:hover { border-color: var(--fs-dim); color: var(--fs-text); }
.td .cbi-button-action, .td .cbi-button-edit, .td .cbi-button-apply,
.cbi-section-actions .cbi-button-edit, .cbi-section-actions .cbi-button-action {
color: var(--fs-accent); border-color: var(--fs-accent-line); background: transparent;
}
/* THE HOVER INK IS --fs-text, NOT THE ROLE COLOUR, and that is the rule this file already
* states twice with measurements: text of colour C on a translucent tint of C eats its own
* contrast (Save: accent on accent-soft = 4.25:1, an AA failure; the alert `.label` chip: 3.4-4.4
* across the palettes). The row actions were the last place it survived — the role still reads,
* from the fill and the border, which is exactly how Save and Reset carry theirs. */
.td .cbi-button-action:hover, .td .cbi-button-edit:hover, .td .cbi-button-apply:hover,
.cbi-section-actions .cbi-button-action:hover {
background: var(--fs-accent-soft); color: var(--fs-text);
border-color: var(--fs-accent-line-hi);
}
/* An enabled/positive toggle in a table cell (System -> Startup) rendered as blue text on a
* green fill = unreadable. Green outline instead, matching the red-outline negative button. */
.td .cbi-button-positive, .cbi-section-actions .cbi-button-positive {
color: var(--fs-good); border-color: var(--fs-good-line); background: transparent;
}
/* …and the border-colour the other two variants already had. Without it the green outline stayed
* at -line while action and negative brightened to -line-hi, so hovering the positive toggle
* read as less responsive than its neighbours in the same row. */
.td .cbi-button-positive:hover, .cbi-section-actions .cbi-button-positive:hover {
background: var(--fs-good-soft); color: var(--fs-text);
border-color: var(--fs-good-line-hi);
}
.td .cbi-button-negative, .td .cbi-button-remove,
.cbi-section-actions .cbi-button-remove, .cbi-section-actions .cbi-button-negative {
color: var(--fs-danger); border-color: var(--fs-danger-line); background: transparent;
}
.td .cbi-button-negative:hover, .td .cbi-button-remove:hover,
.cbi-section-actions .cbi-button-negative:hover {
background: var(--fs-danger-soft); color: var(--fs-text);
border-color: var(--fs-danger-line-hi);
}
/* Page actions: Save & Apply is THE primary action, Save is demoted so only one button reads
* as primary. `--on-color` drives the spinner mask in styles/base. */
.cbi-page-actions .cbi-button-apply,
.cbi-page-actions .cbi-dropdown .cbi-button-apply {
background: var(--fs-accent);
background-image: none;
border: 1px solid transparent;
color: var(--fs-on-accent);
--on-color: var(--fs-on-accent);
text-shadow: none;
}
/* Save carries a TINT at rest, not a bare outline: beside the solid Save & Apply a fully
* transparent button reads as disabled rather than secondary. The steps come off the role
* ladder (02-tokens.css) instead of being invented — -soft (12%) at rest, -fill (18%) on
* hover: one step UP the same ladder.
*
* THE LABEL IS --fs-text, NOT --fs-accent: never put text of colour C on a translucent tint
* of C (docs/design-system.md). The tint drags the surface toward the text and eats its own contrast —
* accent-on-accent-soft measured 4.25:1, an AA failure axe caught the moment the tint went
* in. The role is carried by the fill and the border; the label only has to be legible.
*
* `filter: none` cancels the shared hover's --fs-hover-lift: on a translucent tint a
* brightness shift washes the fill out instead of deepening it. */
.cbi-page-actions .cbi-button-save,
.cbi-page-actions .cbi-button-apply + .cbi-button-save,
.cbi-page-actions .cbi-button-negative + .cbi-button-save {
background: var(--fs-accent-soft);
background-image: none;
border: 1px solid var(--fs-accent-line);
color: var(--fs-text);
--on-color: var(--fs-on-accent);
text-shadow: none;
}
.cbi-page-actions .cbi-button-save:hover {
background: var(--fs-accent-fill);
border-color: var(--fs-accent-line-hi);
filter: none;
}
/* Buttons that are part of a compound field widget. */
.cbi-dynlist > .add-item > button,
.cbi-dynlist > .add-item > .cbi-button-add {
/* min-width was a bare 38px — the same number --fs-ctl-h already states. A restated value
* is the drift this ladder exists to prevent, and it also sat out the Density axis. */
flex: 0 0 auto; width: auto; min-width: var(--fs-ctl-h);
min-height: var(--fs-ctl-h-sm); padding: 0 var(--fs-space-3); margin: 0;
border: 0; border-inline-start: 1px solid var(--fs-border);
border-radius: 0;
background: transparent; color: var(--fs-dim);
}
.cbi-dynlist > .add-item > button:hover,
.cbi-dynlist > .add-item > .cbi-button-add:hover { color: var(--fs-accent); }
.control-group:has(> .cbi-input-password) > button.cbi-button-neutral {
border: 0; border-radius: 0; margin: 0;
background: transparent; color: var(--fs-dim);
padding: 0 var(--fs-space-3); min-height: var(--fs-ctl-h);
border-inline-start: 1px solid var(--fs-border);
}
.control-group:has(> .cbi-input-password) > button.cbi-button-neutral:hover { color: var(--fs-accent); }
/* SQUARE, ON THE LADDER: these were the only buttons in the theme set in bare pixels
* (40 wide x 34 high), and 34 was the one control height that appears in no scale — the ladder
* is --fs-ctl-h-sm/-h/-h-lg = 30/38/44, plus the derived FIELD height (--fs-control-h, 32).
* A literal also opts out of the Density axis: every other control shrinks with
* --fs-density-box and the pager did not. Square because the glyph is an arrow, not a label. */
.pager .btn.prev, .pager .btn.next {
flex: 0 0 auto; width: var(--fs-ctl-h); min-width: var(--fs-ctl-h); height: var(--fs-ctl-h); padding: 0;
border-radius: var(--fs-radius); border: 1px solid var(--fs-border);
background: var(--fs-panel2); color: var(--fs-text);
display: inline-flex; align-items: center; justify-content: center;
}
.pager .btn.prev:hover, .pager .btn.next:hover { border-color: var(--fs-accent); color: var(--fs-accent); }
}