Color system
The theme works with Ionic's standard color palette alone. It does not require
JavaScript, a Capacitor plugin, or a complete Material scheme. Additional Material
color roles are optional refinements of the same component design.
Ionic-only defaults
Import the usual Ionic styles and this theme. Define the standard Ionic named
colors (including contrast), background, and text colors as usual. Both light and
dark palettes use the same rules; there is no separate hardcoded MD3 palette.
Ordinary components retain Ionic's existing color intent. Content and menu use
--ion-background-color; toolbar, card, item, and tab bar retain their dedicated
Ionic background/text variables and their normal fallback chains. No neutral
surface tint is generated for ordinary cards, pages, lists, or overlays.
Explicit color attributes and component --background / --color overrides
remain available. Optional Material roles explicitly refine the underlying Ionic palette.
Component --background / --color overrides remain the final application control.
Supplying an optional Material role refines only its consumers. Unspecified roles
fall back to the component's Ionic colors; supplying one role does not turn on an
entire generated neutral palette. Light and dark mode use the same mechanism.
Explicit variants (.md3-tonal, .md3-elevated, .md3-filled,
.md3-outlined, .md3-selected) intentionally request additional color treatments.
Their missing container/outline colors can be derived from the Ionic palette using
CSS color mixing. This derivation belongs to those requested variants, rather than
changing every uncolored component. Accent controls retain Ionic primary/contrast
when optional Material accent/container pairs are absent.
Optional roles
Override only the roles you need, on the root or a local container:
.my-section {
--md-sys-color-secondary-container: #d8e2ff;
--md-sys-color-on-secondary-container: #001a41;
}
.ion-palette-dark .my-section {
--md-sys-color-secondary-container: #243e68;
--md-sys-color-on-secondary-container: #d8e2ff;
}
Provide foreground/background role pairs together when changing a pair. Omitted
roles still follow Ionic. No separate registration or complete scheme is needed.
Material You is one optional provider of these values. See
dynamic colors for native acquisition and appearance control.
The provider preserves Material's on* roles for MD3 components; Ionic named
colors use supplied onPrimary, onSecondary, onTertiary and onError for their
contrast values, with Ionic's contrast calculation as the per-role fallback.
Shade and tint retain Ionic's calculations. The native
API also exposes optional fixed/inverse roles for custom component designs.
For your own Sass components, import utils/colors from the package's exported
src/styles path and use color(secondary-container) under that module's
namespace. The function emits an optional role plus the same Ionic fallback.
It supports the roles defined in utils/_colors.scss and fails the Sass build
on unknown roles. Its generated container fallbacks are intended for explicitly
requested variants; ordinary components retain their own Ionic fallback chains. For plain CSS custom components, use the same var(--md-sys-color-..., ...)
pattern with an Ionic fallback.
Component assignments
For ordinary surfaces, base means surface-container-lowest in light mode and
surface-container-low in dark mode. Panel means surface-container-lowest
in light mode and surface-container-high in dark mode. Missing roles retain the
component's Ionic background fallback. This keeps default light surfaces white;
color="light" is a separate palette selection (surface-container-low in light,
surface-container-highest in dark), not an alias for every default background.
The table lists optional Material role consumers. If a role is absent, the
component retains the Ionic fallback described above.
| Component / variant | Background | Foreground / detail |
|---|---|---|
| filled button | primary | on-primary |
tonal button (md3-tonal, solid fill) |
secondary-container | on-secondary-container |
elevated button (md3-elevated, solid fill) |
surface-container-low | primary |
| outlined / text button | transparent | primary; outline border for outlined |
| card (default elevated) | Panel | on-surface; on-surface-variant subtitle |
filled card (md3-filled) |
surface-container-highest | on-surface |
outlined card (md3-outlined) |
surface | on-surface; outline-variant border |
| FAB | primary-container | on-primary-container |
chip / selected chip (md3-selected) |
surface-container-low / secondary-container | on-surface-variant / on-secondary-container |
| content / menu | Base | on-surface |
| toolbar | Panel | on-surface |
| list / ordinary list item | Panel | on-surface |
| inset list item | Panel; surface before Ionic fallback when panel role is absent | on-surface; transparent 2px gap |
| navigation bar | Panel | on-surface-variant; secondary-container selected indicator / on-secondary-container selected icon |
| segment | inherited surface | primary selected; on-surface-variant unselected |
| search field | surface-container-high; surface-container-highest inside an uncolored dark toolbar/item | on-surface; on-surface-variant icon; Ionic placeholder behavior |
| switch, checkbox, radio, progress, slider | neutral surface or secondary-container when off/inactive; primary when selected | matching on-primary, outline, or on-surface-variant |
| text input | Ionic fill-state background | on-surface; primary focus; error invalid state |
| modal / popover | Base | Ionic text; nested components resolve their own roles; select popover options share the overlay background |
| action sheet | Base | on-surface actions; primary preferred action; error destructive action; on-surface-variant title |
| alert | Base | on-surface; preferred primary/on-primary; destructive error/on-error; outlined cancel |
| loading / datetime / picker | surface-container-high | on-surface; primary actions/spinner |
| snackbar/toast | inverse-surface | inverse-on-surface; inverse-primary actions |
The existing Ionic color="..." remains an explicit palette selection. These
MD3 defaults do not replace it. md3-disabled opts an individual component out
of MD3 styling. Existing per-component CSS variables can still override the
chosen role through the normal cascade. Variant classes affect appearance only;
md3-selected does not implement selection behavior or accessibility state.
<ion-button class="md3-tonal" fill="solid">Tonal action</ion-button>
<ion-card class="md3-filled">Filled card</ion-card>
<ion-button color="danger">Explicit Ionic danger palette</ion-button>
The demo's Color palette page compares the Ionic-only result with locally
supplied additional roles. Browser tests check both appearances, local Ionic
changes, optional-role removal, explicit colors, opt-outs, and a document with
application JavaScript disabled.
Android 17 Settings is the visual reference; Material guidelines supply supporting
information. The base/panel roles above refine ordinary surfaces without
replacing their Ionic color intent. Without those roles, Ionic backgrounds remain
unchanged. Groups retain 20px outer corners and 2px gaps. The assignments above
name optional roles; they do not imply generated surface colors for omitted roles.
Icon-only toolbar back buttons retain a 56px touch target and geometry for a 40px circle.
That circle is transparent by default; supplying --md-sys-color-surface-container-highest
paints it. An explicitly colored toolbar retains a transparent back-button background
and Ionic's contrast foreground. Set --md3-back-background on the back button to
customize its paint.
Icon-free switches use a 52×28px track and a 20px thumb, measured from the Pixel
Android 17 Internet panel. CSS-only installation retains Ionic's normal
thumb and its enableOnOffLabels API. The standard toggle and demo use an icon-free thumb.
Component md3-disabled and explicit Ionic colors remain available.
Typography, Ionic back-button responsibilities and switch state icons are documented
in Android Settings appearance. They do not change the
palette or require device colors.
The theme's colors and geometry work without optional JavaScript helpers or a Capacitor plugin.
For a range on an arbitrary HTML surface, set --md3-range-gap-color to that surface
color; standard Ionic containers supply their background automatically.
For measured tablet dialog sizing, corners, keyboard behavior, and the historical
native reference captures, see Android 17 tablet review.
For the subsequent Ionic-default color correction and its browser verification, see
Ionic color inheritance review.
Tablet Alert sizing
Wide tablet Alerts follow the measured Android 17 Settings proportions: 85% in
portrait and 65% in landscape. Android tablet typing keeps that width when the
IME resizes the viewport. Phone-sized windows retain Ionic's content-sized layout.
Use the public sizing properties to customize a dialog. For a controller-created
Alert, set cssClass: 'app-alert' and place a component-qualified rule in the
application's global stylesheet after the theme:
ion-alert.app-alert {
--width: 400px;
--max-width: min(400px, calc(100vw - 32px));
}
Inline style bindings work as well. The ordinary CSS minimum/maximum constraints
still apply, and md3-disabled restores Ionic's own sizing defaults.