-
Notifications
You must be signed in to change notification settings - Fork 0
LayrzAlert
A Material-free inline status callout that communicates information, success, warnings, or errors with semantic color, icons, and flexible visual styles.
Metadata
Mirrors: ThemedAlert (layrz_theme)
Phase: M2 (Core primitives)
Domain: Feedback
Primitive: Hand-rolled (Container + BoxDecoration + Row + Column + Icon)
Status: Confirmed scope.
LayrzAlert is an inline status callout that displays a semantic message via color, icon, title text, and body text. It supports multiple visual styles and semantic types to fit different contexts — from informational hints to critical error callouts. The alert can be either non-interactive (default, read-only) or interactive (with optional tap handling and hover/press feedback).
-
Semantic type mapping — the
typeparameter maps to a semantic color (info, success, warning, danger, context, or custom), automatically selecting both the icon and color without explicit parameters. -
Fixed content structure —
titleanddescriptionare both required; there are no optional variants. This enforces a consistent information hierarchy and prevents incomplete callouts. -
Two visual styles —
layrz(default, tonal) andfilledIcon(high-emphasis, solid), both using split-panel layout with colored left panel and surface right panel. - Responsive layout — no fixed heights on text-bearing elements, supporting WCAG 1.4.4 (Text Scaling, AAA) — text scales with the system font size without causing layout issues.
-
Optional interactivity — alerts are read-only by default (
onTap: null). WhenonTapis provided, the alert becomes interactive with hover, press, and focus feedback. A containing view manages the alert's lifecycle. - Geometry consistency — per decision D15, only color and shadow change; size, padding, and radius remain constant across all interaction states. The lift during hover/focus is paint-only (via transform), never affecting layout or hit-testing.
class LayrzAlert extends StatelessWidget {
/// The semantic type of the alert.
///
/// Determines the default icon and colour. Defaults to [LayrzAlertType.info].
/// If [type] is [LayrzAlertType.custom], the [color] and [icon] parameters
/// control appearance; otherwise they are ignored.
final LayrzAlertType type;
/// The title text of the alert.
///
/// Required. Displays as bold title text. Scales with system text scale factor.
final String title;
/// The description text of the alert.
///
/// Required. Displays as body text. Limited to [maxLines] lines before ellipsis.
/// Scales with system text scale factor.
final String description;
/// The maximum number of lines for [description] text.
///
/// If the description exceeds this many lines, it is truncated with an ellipsis.
/// Defaults to 3.
final int maxLines;
/// The visual style of the alert.
///
/// Determines background, border, icon chip, and text colours.
/// Defaults to [LayrzAlertStyle.layrz].
final LayrzAlertStyle style;
/// The custom colour of the alert, used only when [type] is [LayrzAlertType.custom].
///
/// If [type] is not custom, this parameter is ignored.
/// If null and [type] is custom, defaults to [LayrzTokens.colors.primary].
final Color? color;
/// The custom icon glyph, used only when [type] is [LayrzAlertType.custom].
///
/// If [type] is not custom, this parameter is ignored.
/// If null and [type] is custom, defaults to [MdiIcons.informationBoxOutline].
final IconData? icon;
/// The size of the icon glyph.
///
/// If null, defaults to [kLayrzAlertFilledIconSize] for [LayrzAlertStyle.filledIcon],
/// or [kLayrzAlertIconSize] for all other styles.
final double? iconSize;
/// Called when the user taps the alert.
///
/// When null (the default), the alert is not interactive: no cursor change,
/// no hover/press feedback, not focusable, and not announced as a button to
/// assistive technology. The alert renders exactly as it did before, with no
/// state changes during interaction.
///
/// When non-null, the alert becomes interactive: on hover or focus, the surface
/// lifts by [kLayrzAlertHoverLift] and the shadow steps up one level; on press,
/// it settles back down. The lift is paint-only (via transform), so geometry
/// remains constant. The alert is focusable by Tab and activatable by Enter or
/// Space, and announces as a button to assistive technology.
final VoidCallback? onTap;
/// Creates a [LayrzAlert].
const LayrzAlert({
super.key,
this.type = LayrzAlertType.info,
required this.title,
required this.description,
this.maxLines = 3,
this.style = LayrzAlertStyle.layrz,
this.color,
this.icon,
this.iconSize,
this.onTap,
});
}Semantic type classification that determines the alert's accent color and icon:
enum LayrzAlertType {
/// Informational semantic type — use `tokens.colors.info` for neutral messages.
///
/// Associated icon: [MdiIcons.informationBoxOutline].
info,
/// Success semantic type — use `tokens.colors.success` for positive/affirmative messages.
///
/// Associated icon: [MdiIcons.checkboxOutline].
success,
/// Warning semantic type — use `tokens.colors.warning` for cautionary messages.
///
/// Associated icon: [MdiIcons.alertBoxOutline].
warning,
/// Danger semantic type — use `tokens.colors.danger` for destructive/critical messages.
///
/// Associated icon: [MdiIcons.closeCircleOutline].
danger,
/// Contextual semantic type — use `tokens.colors.contextual` for context-dependent messages.
///
/// Associated icon: [MdiIcons.dotsSquare].
context,
/// Custom type — use explicit `icon` and `color` values from the [LayrzAlert] constructor.
///
/// The `color` and `icon` parameters are only honoured when `type == custom`.
/// Defaults to `tokens.colors.primary` and [MdiIcons.informationBoxOutline] if
/// `color` and `icon` are null, respectively.
custom,
}| Type | Token Color | Icon | Use Case |
|---|---|---|---|
| info |
tokens.colors.info (blue) |
informationBoxOutline |
Neutral, informational messages |
| success |
tokens.colors.success (green) |
checkboxOutline |
Confirmation, successful operations |
| warning |
tokens.colors.warning (orange) |
alertBoxOutline |
Cautionary, attention-needed messages |
| danger |
tokens.colors.danger (red) |
closeCircleOutline |
Critical, destructive, error messages |
| context | tokens.colors.contextual |
dotsSquare |
Context-dependent, custom application logic |
| custom | Provided via color parameter |
Provided via icon parameter |
Any other semantic meaning |
Visual style classification that determines appearance. All styles render in a split-panel layout:
enum LayrzAlertStyle {
/// Default Layrz style with tonal accent on left, neutral surface on right, severity-tinted border.
layrz,
/// Solid accent on left panel, neutral surface on right, solid accent border.
filledIcon,
}All styles use a split-panel layout. The table below shows how each style handles color rendering:
| Style | Left Panel | Right Panel | Border | Title Color | Body Color | Icon Color |
|---|---|---|---|---|---|---|
| layrz | Tonal accent (20% opacity) | surface |
Solid accent | fg1 |
fg2 |
Accent |
| filledIcon | Solid accent | surface |
Solid accent | fg1 |
fg2 |
Contrast |
Tonal opacity: The tonal accent is computed as accent.withOpacityValue(tokens.colors.tonalOpacity).
Contrast color: For solid-colored backgrounds, the text and icon use contrastColor to ensure readability (white or black depending on accent brightness).
The .layrz style is the default and most versatile. All alerts use a split-panel layout that combines clarity with visual softness:
-
Left panel: tonal accent background (at
tokens.colors.tonalOpacity, typically 20% opacity) with the semantic icon centered - Right panel: neutral surface background with title and description
-
Border: solid accent at
tokens.border.base, defining the alert's perimeter - Icon: accent color at full strength for semantic visibility
-
Text: neutral foreground colors (
fg1for title,fg2for body), ensuring strong readability
This style creates visual hierarchy through the tonal left panel while maintaining readability via the neutral right panel. It works universally on neutral backgrounds and is the safe choice for inline alerts in forms, cards, and dialogs. The tonal left panel softens the accent emphasis compared to the more aggressive filledIcon style, making .layrz suitable for general-purpose status messages.
All alert styles use a split-panel layout:
A row split into two vertical panels with equal heights:
-
Left panel: colored background (tonal for
layrz, solid forfilledIcon) with a centered icon (kLayrzAlertFilledIconSize= 25.0 logical pixels) - Right panel: neutral surface background with title and description
-
Border: drawn via
foregroundDecorationover both panels to avoid antialiasing seams
Both panels use tokens.spacing.sp3 padding (16 logical pixels).
┌────────────┬────────────────────────┐
│ ⊕ accent │ Title Bold │
│ (tonal or │ Description text... │
│ solid) │ │
└────────────┴────────────────────────┘
Both title and description are required. Providing neither or only one triggers a constructor assertion error.
-
title(String, required): Bold, single-line header text. Displays intokens.typography.titlewithfontWeight.bold. -
description(String, required): Body text. Displays intokens.typography.body. Limited tomaxLines(default 3) before ellipsis truncation.
This strict 1:1 structure is a deliberate port of ThemedAlert — every alert has a title and body, preventing incomplete or ambiguous callouts.
LayrzAlert supports optional interactivity via the onTap callback. Non-interactive alerts (the default, with onTap: null) remain read-only with no state changes. Interactive alerts (with onTap: non-null) respond to hover, press, and focus with visual feedback.
Non-interactive (onTap: null):
- No cursor change; uses the default pointer cursor.
- No hover or press visual feedback.
- Not focusable via keyboard Tab navigation.
- Announces as a container to assistive technology, not a button.
- Rendering is identical to the pre-interactive alert design.
Interactive (onTap: non-null):
-
Cursor: Changes to
SystemMouseCursors.clickon hover. -
Hover: Surface lifts by
kLayrzAlertHoverLift(4.0 logical pixels) and shadow steps up one level (viaAnimatedContainer). Animation usestokens.motion.dHoverandtokens.motion.easingEnter. - Focus: Renders identically to hover — surface lifts and shadow steps up. Reachable via Tab navigation.
-
Press without prior hover (touch device): Surface lifts by
kLayrzAlertHoverLiftand shadow steps up to elevation 2 — the same treatment as hover. Touch devices cannot hover, so the alert gives immediate visual feedback on press to indicate the action is recognized. - Press with prior hover (desktop): Surface settles back down to its resting position; shadow steps down to elevation 1.
-
Keyboard Activation: Enter or Space key activates the alert (calls
onTap). Activation is automatic insideLayrzAppviaWidgetStateIntent/ActivateIntentbinding; no explicit shortcut configuration is needed. - Accessibility: Announces as an interactive button with enabled state.
The hover/focus lift is implemented as a paint-only transform (Matrix4.translationValues(0, -_currentLift, 0)), not a layout change. This means:
- The alert's bounding box and hit-test region remain constant across all interaction states.
- No reflow or repositioning of surrounding widgets.
- The pointer is not carried away by a moving surface, preventing hover oscillation loops (the same failure pattern
LayrzTooltipencountered and resolved viaLayrzTooltip's anchor-always-visible guarantee).
This design satisfies the spirit of decision D15 (geometry consistency during interaction) while adding lift feedback. See Decisions (D15) for the broader context, and the amendment recorded on 2026-08-16 permitting paint-only transforms under controlled conditions.
class DismissibleAlertExample extends StatefulWidget {
@override
State<DismissibleAlertExample> createState() => _DismissibleAlertExampleState();
}
class _DismissibleAlertExampleState extends State<DismissibleAlertExample> {
bool showAlert = true;
@override
Widget build(BuildContext context) {
if (!showAlert) {
return SizedBox.shrink();
}
return LayrzAlert(
type: LayrzAlertType.warning,
title: 'Important Notice',
description: 'Tap this alert to dismiss it.',
onTap: () {
setState(() => showAlert = false);
},
);
}
}In this example, the alert becomes interactive and provides visual feedback (lift and shadow) on hover/focus. When tapped or activated via keyboard, it calls the onTap callback, which dismisses the alert. Non-interactive alerts are the default and remain the common case for informational callouts that do not require user action.
| Property | Value |
|---|---|
| Container padding |
tokens.spacing.sp3 (16 logical pixels all sides) |
| Border radius |
tokens.radius.r3 (16 logical pixels) |
| Icon glyph size |
kLayrzAlertFilledIconSize = 25.0 logical pixels |
| Gap between panels | Integrated into split-panel layout |
| Gap between title and description |
tokens.spacing.sp1 (4 logical pixels) |
| Max description lines |
maxLines parameter (default 3) |
No fixed heights on text: Title and description elements have no explicit height constraints, allowing them to scale with the system font size (WCAG 1.4.4, AAA).
For the split-panel styles (.layrz and .filledIcon), the border is painted via foregroundDecoration over the clipped content, rather than in a surrounding BoxDecoration. This prevents visible antialiasing seams at the boundary between the left and right panels. The outer ClipRRect applies the corner radius, and the border is painted on top at the same radius.
When an alert is interactive (onTap is non-null), its translucent background colours are flattened to opaque equivalents using the flattenOn extension method. This is necessary because BoxDecoration paints its shadow behind the decorated box, and any translucent fill lets the shadow show through as a smudge inside the box instead of beneath it. By compositing translucent colours onto the surface they will be painted over, the result is pixel-identical but fully opaque — so shadows render correctly.
Inert alerts (the default, with onTap: null) preserve true translucency and are unchanged.
A standalone circular icon chip widget that renders a semantic alert icon. It is NOT directly used by LayrzAlert — instead, LayrzAlert builds its own icon chips internally. This widget is provided as a reusable building block for other contexts.
class LayrzAlertIcon extends StatelessWidget {
/// The semantic type of the alert icon.
///
/// Determines the default icon and colour if [icon] or [color] are not provided.
/// Defaults to [LayrzAlertType.info].
final LayrzAlertType type;
/// The outer size of the icon chip container (width and height in logical pixels).
///
/// Defaults to [kLayrzAlertIconWidgetSize].
final double size;
/// The size of the icon glyph inside the chip.
///
/// Defaults to [kLayrzAlertIconWidgetIconSize].
final double iconSize;
/// Padding between the chip edge and the icon glyph.
final EdgeInsetsGeometry? padding;
/// The colour of the icon chip, used only when [type] is [LayrzAlertType.custom].
///
/// If null and [type] is [LayrzAlertType.custom], defaults to
/// [LayrzTokens.colors.primary]. Ignored for non-custom types.
final Color? color;
/// The icon glyph, used only when [type] is [LayrzAlertType.custom].
///
/// If null and [type] is [LayrzAlertType.custom], defaults to
/// [MdiIcons.informationBoxOutline]. Ignored for non-custom types.
final IconData? icon;
/// Creates a [LayrzAlertIcon].
const LayrzAlertIcon({
super.key,
this.type = LayrzAlertType.info,
this.size = kLayrzAlertIconWidgetSize,
this.iconSize = kLayrzAlertIconWidgetIconSize,
this.padding,
this.color,
this.icon,
});
}Standalone usage:
// Info icon chip
LayrzAlertIcon(
type: LayrzAlertType.info,
size: 40,
iconSize: 24,
)
// Custom icon chip
LayrzAlertIcon(
type: LayrzAlertType.custom,
color: Colors.purple,
icon: MdiIcons.heart,
)LayrzAlert is built entirely without Material or Cupertino imports, using only package:flutter/widgets.dart primitives:
- Container — background and border rendering
- Row — horizontal layout for icon and text
- Column — vertical layout for title and description
- Icon — semantic icon rendering
- Text — text rendering with styling
-
ClipRRect — corner radius clipping for
filledIconstyle -
IntrinsicHeight — height matching between panels in
filledIconstyle
LayrzAlert(
type: LayrzAlertType.info,
title: 'Information',
description: 'This is an informational message for the user.',
)LayrzAlert(
type: LayrzAlertType.success,
title: 'Operation Successful',
description: 'Your changes have been saved.',
style: LayrzAlertStyle.layrz, // Default; omit for the same effect
)LayrzAlert(
type: LayrzAlertType.warning,
title: 'Warning',
description: 'This action requires your confirmation. Please review carefully before proceeding.',
maxLines: 4,
style: LayrzAlertStyle.layrz,
)LayrzAlert(
type: LayrzAlertType.danger,
title: 'Error',
description: 'An unexpected error occurred. Please try again later.',
style: LayrzAlertStyle.filledIcon, // High-emphasis solid accent
)LayrzAlert(
type: LayrzAlertType.context,
title: 'Context-Dependent',
description: 'This alert\'s meaning depends on the surrounding application state.',
style: LayrzAlertStyle.filledIcon,
)LayrzAlert(
type: LayrzAlertType.custom,
title: 'Custom Alert',
description: 'Using a custom color and icon.',
color: Colors.purple,
icon: MdiIcons.heart,
)Container(
padding: EdgeInsets.all(16),
child: Column(
children: [
LayrzAlert(
type: LayrzAlertType.info,
title: 'Form Instructions',
description: 'Please fill in all required fields marked with an asterisk (*).',
),
SizedBox(height: 24),
// Form fields follow...
],
),
)LayrzAlert(
type: LayrzAlertType.success,
title: 'Update Available',
description: 'A new version is ready. Tap to install.',
style: LayrzAlertStyle.layrz,
onTap: () {
// Handle the tap — e.g., initiate download/installation
handleUpdateInstall();
},
)When onTap is provided, the alert lifts on hover and announces as a button. Non-interactive alerts (onTap: null) remain the default and recommended pattern for informational callouts that require no user action.
Choose the alert style based on the context where it appears:
| Context | Recommended Style | Rationale |
|---|---|---|
| Default / most contexts |
.layrz (default) |
Tonal left panel provides semantic emphasis without overwhelming; split-panel layout creates visual hierarchy and works universally |
| High-emphasis / critical message | .filledIcon |
Solid accent left panel with icon commands stronger attention; use when the message requires immediate action |
-
M1 Theme System (
LayrzTheme,LayrzThemeData,LayrzTokens) — colors, typography, spacing, radius, and border tokens -
Flutter primitives (
Container,Row,Column,Icon,Text,ClipRRect) — frompackage:flutter/widgets.dart -
flutter_material_design_icons (^3.1.0) — icon constants (bare
IconData, not wrapped)
-
Semantics:
- Non-interactive alerts (
onTap: null) are wrapped inSemantics(container: true, label: '<title>. <description>'), announcing as a container. - Interactive alerts (
onTap: non-null) are wrapped inSemantics(button: true, enabled: true, label: '<title>. <description>'), announcing as an enabled button.
- Non-interactive alerts (
-
Keyboard activation: Interactive alerts are reachable via Tab and activatable by Enter or Space. No explicit shortcut binding is needed;
LayrzApp(viaWidgetStateIntentroutes) supplies the binding automatically. - Color not the sole cue: the icon is a non-colour severity cue, satisfying WCAG 1.4.1 (Use of Color, A).
- Text scaling: no fixed heights on text elements support WCAG 1.4.4 (Text Scaling, AAA) — alerts remain usable at 2x system font size.
- Contrast: title and body text colors are resolved with sufficient contrast against their backgrounds per WCAG 1.4.3 (Contrast, AA).
- Focus visible: Interactive alerts show focus via shadow elevation (lifted state), satisfying WCAG 2.4.7 (Focus Visible, AA).
Last updated: 2026-08-16
Related documents: Milestone 2, Design Tokens, Architecture, Decisions (D15), Roadmap
Made with ❤️ by Golden M, Inc.
- LayrzTextInput
- LayrzSelectableAction
- LayrzSelectionToolbar
- LayrzTextSelectionControls
- LayrzSelectionMagnifier
- LayrzSelectionHandlePainter
- LayrzTextAreaInput
- LayrzComboBoxInput
- LayrzNumberInput
- LayrzPasswordInput
- LayrzCheckboxInput
- LayrzRadioInput
- LayrzSelectInput
- LayrzMultiSelectInput
- LayrzSearchInput
- LayrzDualListInput
- LayrzDurationInput