# Styling and host appearance

Mango gives extension documents the active app appearance without taking ownership of their layout. The host injects palette tokens as CSS custom properties, presents the page on a transparent WebView canvas, and makes CSS `prefers-color-scheme` follow the active Mango palette. The extension still owns every selector, reset, font, spacing rule, and component style.

There is no Mango stylesheet to import. Use the documented custom properties directly and provide browser-safe fallbacks so the page remains usable in ordinary browser development tools. Newer Mango 3.1 builds also inject optional button and inset-label field styles described below; existing extension controls are not changed automatically.

## Theme tokens

Mango sets these properties on the document root before the entry document starts:

| Property | Intended use |
|---|---|
| `--grove-bg` | Main pane or page background. The host canvas is transparent, so `transparent` is also appropriate when the page should blend directly into Mango. |
| `--grove-surface` | Subtle raised or grouped surface, such as a result tile or secondary button. |
| `--grove-surface-strong` | More prominent raised surface or hover/selected state. |
| `--grove-text` | Primary text. |
| `--grove-text-secondary` | Supporting text, labels, and descriptions. |
| `--grove-text-tertiary` | Quiet metadata and placeholders. |
| `--grove-link` | Links and other link-like text. |
| `--grove-hairline` | Subtle separators and borders. |
| `--grove-hairline-strong` | More visible control borders and dividers. |
| `--grove-accent` | Primary accent, focus state, or action fill. |
| `--grove-accent-soft` | Translucent accent wash for selection, focus rings, and low-emphasis highlights. |

Values are complete CSS `rgb(...)` or `rgba(...)` colors. Treat their visual values as dynamic: a user can switch Mango themes while the extension remains running.

Use each token for its semantic role rather than assuming a particular hue or brightness. For example, `--grove-accent` is not guaranteed to be purple and `--grove-bg` is not guaranteed to be dark.

## Start with a small reset

This is a useful baseline for a visible Sprig or settings page:

```css
:root {
  color-scheme: light dark;
  font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
}

* {
  box-sizing: border-box;
}

html,
body {
  min-width: 0;
  min-height: 100%;
  margin: 0;
}

body {
  color: var(--grove-text, CanvasText);
  background: transparent;
}

a {
  color: var(--grove-link, LinkText);
}
```

Use `background: var(--grove-bg, Canvas)` instead when the document needs to paint an opaque page of its own. `CanvasText`, `Canvas`, `GrayText`, `LinkText`, `Highlight`, and `ButtonFace` are browser system colors that make useful fallbacks outside Mango.

## Controls that fit Mango

### Optional shared buttons

Mango 3.1 builds with the voice-recording UI update inject an opt-in `grove-button` class for native HTML buttons. Add `grove-button-quiet` for a transparent secondary action:

```html
<button class="grove-button">Record</button>
<button class="grove-button grove-button-quiet">Discard</button>
```

The shared style uses live Grove palette tokens, rounded corners, hover and pressed backgrounds, a keyboard-focus outline, and disabled feedback. Hover applies only to enabled controls on hover-capable devices. The default minimum height is 28 CSS pixels (40 on coarse-pointer devices); the extension still owns layout and can override base sizing. Styling never applies to buttons without the class, and never to external web dashboards. It requires no new permission.

Keep local fallback styling for ordinary browsers and earlier Mango builds, which ignore these class names. No JavaScript capability detection is needed. Do not use the class as a security or host-version signal.

### Optional inset-label fields

Newer Mango 3.1 builds inject an opt-in `grove-field` style. The extension supplies a semantic label and its own text; Mango does not add labels or restyle ordinary inputs:

```html
<label class="grove-field">
  <span class="grove-field-label">File name</span>
  <input type="text" placeholder="voice-message" aria-label="File name (optional)">
</label>
```

The small muted label stays inside the border above the editable value. The field uses live palette tokens, hover feedback, a visible keyboard-focus outline around the wrapper, and disabled feedback. Use it for single-line text inputs, not checkboxes, file pickers, or other specialized input types. Only direct input children of `label.grove-field` receive these styles; existing Giphy, Immich, and other unmarked fields remain unchanged.

The minimum height is 38 CSS pixels (44 on coarse-pointer devices, with a 16px input font to avoid focus zoom). The extension still owns width, flex sizing, and spacing between controls. Hide the whole label wrapper with `hidden` when needed. Keep local fallback styling for older Mango builds and browser development, just as with `grove-button`. No new permission or JavaScript capability check is required.

#### Floating-label variant

Add `grove-field-floating` to the same wrapper for a button-height field. Its label sits inside while empty, then animates above the border on focus or when it has a value. The floated label is 9px (10px on touch devices); the field is at least 28px tall (40px on touch devices). Reduced-motion users get the same states without animation.

```html
<label class="grove-field grove-field-floating">
  <input type="text" placeholder="voice-message" aria-label="File name (optional)">
  <span class="grove-field-label">File name</span>
</label>
```

Keep a `placeholder` attribute (`" "` is sufficient if no hint is needed): CSS uses `:placeholder-shown` to detect an empty field. The placeholder hint only appears while focused. Leave at least 16px above the field for the floating label, and do not clip it with an overflow-hidden wrapper. As with the inset variant, ship fallback CSS when supporting earlier hosts. Other fields remain unchanged.

### Custom controls

Keep the control structure owned by the extension and compose it from host tokens:

```css
input,
select,
textarea,
button {
  min-width: 0;
  border: 1px solid var(--grove-hairline-strong, color-mix(in srgb, CanvasText 24%, transparent));
  border-radius: 0.55rem;
  color: var(--grove-text, CanvasText);
  background: var(--grove-surface, ButtonFace);
  font: inherit;
}

input,
select,
textarea {
  padding: 0.55rem 0.7rem;
}

input:focus-visible,
select:focus-visible,
textarea:focus-visible,
button:focus-visible {
  outline: 2px solid var(--grove-accent, Highlight);
  outline-offset: 2px;
}

button.primary {
  border-color: transparent;
  color: Canvas;
  background: var(--grove-accent, Highlight);
}

.panel {
  border: 1px solid var(--grove-hairline, color-mix(in srgb, CanvasText 18%, transparent));
  border-radius: 0.8rem;
  background: var(--grove-surface, color-mix(in srgb, CanvasText 7%, transparent));
}

.help {
  color: var(--grove-text-secondary, GrayText);
}
```

Test primary-button text against every bundled Mango palette. A fixed light or dark foreground can lose contrast with a user-created theme; when the action does not need a filled treatment, an accent-colored label on `--grove-surface` is safer.

## Live palette changes

Mango updates the root properties without reloading the document. CSS declarations that reference `var(--grove-...)` repaint automatically and preserve JavaScript state, scroll position, and form contents.

Do not read a token once with `getComputedStyle(...)`, convert it to a hard-coded value, and expect that value to stay current. If JavaScript must draw into a canvas, re-read the computed token when `matchMedia('(prefers-color-scheme: dark)')` changes and whenever the page becomes visible again. Ordinary HTML and SVG should prefer CSS properties so no JavaScript synchronization is needed.

The resolved Mango palette also drives `prefers-color-scheme`, including when the palette's light or dark appearance differs from the system setting. Use the media query for assets or layout choices that genuinely differ by appearance; use the Grove tokens for colors.

## UI scale and CSS units

On macOS, Mango applies its current app UI scale to running extension views through WebKit page zoom. WebKit defines page zoom as scaling page content relative to the viewport, equivalent to applying CSS `zoom` to all page content. Mango reapplies it when the user changes the app's typography size and applies it to newly created and retained extension WebViews.

The scale is host-owned and is not exposed as a CSS custom property or JavaScript value. Do not infer it from viewport dimensions and do not multiply font sizes, padding, icons, or controls by a second scale factor. A declaration such as `font-size: 14px` and one such as `font-size: 1rem` are both scaled by the host.

Relative units are still recommended for relationships inside the extension:

- use `rem` for the extension's typography and spacing scale;
- use `em` when a component should follow its own text size;
- use `%`, `fr`, `minmax(...)`, `min(...)`, `max(...)`, and `clamp(...)` for flexible layout;
- reserve CSS pixels for details such as one-pixel borders or a deliberate minimum.

Host zoom does not make a fixed layout responsive. A channel pane, network tab, composer surface, floating window, settings page, iPhone, and iPad can all provide different viewport sizes. Do not size the page from `presentation.defaultSize`; that value is only an initial host preference.

Current API 1 host zoom is a macOS behavior for running extension views. Extensions must remain usable at the browser's ordinary CSS scale on iOS, iPadOS, settings pages, and browser-based development. This is why responsive CSS is part of the extension contract even when a Sprig primarily targets macOS.

## Responsive surface recipe

Let the surface provide the outer size and let the document adapt:

```css
.app {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  width: 100%;
  min-width: 0;
  min-height: 100dvh;
  padding: clamp(0.75rem, 2.5vw, 1.25rem);
  gap: 0.75rem;
}

.results {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(9rem, 100%), 1fr));
  min-width: 0;
  overflow: auto;
}

@media (max-width: 28rem) {
  .actions {
    display: grid;
    grid-template-columns: 1fr;
  }
}

@media (pointer: coarse) {
  button,
  input,
  select {
    min-height: 44px;
  }
}
```

Avoid viewport-wide fixed widths, fixed heights around scrolling content, and desktop-only hover interactions. Use `min-width: 0` on grid and flex children that contain user or IRC text so long content cannot force the surface wider than Mango gives it.

## Accessibility and motion

- Preserve visible keyboard focus with `:focus-visible`.
- Do not encode state only with color; add text, an icon, or another shape.
- Allow text to wrap and test with long localized labels.
- Respect `prefers-reduced-motion` for nonessential animation.
- Use native semantic elements before recreating controls with generic elements.
- Test the extension in every bundled Mango palette, not only system Light and Dark.

Continue with [Surfaces and scopes](surfaces-and-scopes.md) for the sizes and contexts in which a page can appear, and [Storage and settings](storage-and-settings.md) for the more restricted settings-page runtime.
