Interface
Shared rules for the client. Screen behaviour for a record lives on that record's page.
Adaptive buttons
A button that already carries a Feather icon and a short action name shows both from the lg breakpoint up. Below 992px the label is hidden and the icon remains. That width covers a phone, the installed app, and a narrowed browser window. The installed app uses the same viewport; there is no separate display-mode rule.
The cut is the Phoenix lg breakpoint, the same one as the phone tap targets in static/tempest/css/app.css (max-width: 991.98px). The 1200px layouts for the location page and the list map do not choose the label.
Mark the control once:
<a class="btn btn-primary tp-touch" href="..." aria-label="Edit details">
<svg class="me-lg-1" aria-hidden="true"><!-- Feather icon from icon() --></svg>
<span class="d-none d-lg-inline">Edit details</span>
</a>
icon() in static/tempest/js/util.js already sets aria-hidden on the SVG. Pass me-lg-1 as its class so the gap sits only beside a visible label. d-none removes the words from the accessibility tree, so aria-label repeats them. .tp-touch keeps the control at least 44px tall below 992px.
An adaptive button stays a normal .btn.tp-touch. btn-icon is for a control that is icon-only at every width, such as the visit modify button, and that control keeps its own aria-label.
Save, Cancel, and Delete keep their words at every width. Delete on a viewpoint row is the exception: it follows this rule.
The controls that use it:
- The location header in
static/tempest/js/ui/location.js: Edit details, Forecast, and Move to another location. - Each viewpoint row on that page: Edit, Up, Down, and Delete. Set as primary is not on the row.
- The trip header in
static/tempest/js/ui/trip.js: Assistant, Map, and Edit. Kit stays labeled, including the packed count (Kit 3/8). - Copy on a coordinate row in
static/tempest/js/ui/components.js. Open in Google Maps on that same block stays labeled.