-
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. -
Five visual styles —
layrz(default),filledTonal,filled,outlined, andfilledIcon, each with a distinct visual treatment suited to different container contexts. - 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 [LayrzIcons.solarOutlineInfoSquare].
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: [LayrzIcons.solarOutlineInfoSquare].
info,
/// Success semantic type — use `tokens.colors.success` for positive/affirmative messages.
///
/// Associated icon: [LayrzIcons.solarOutlineCheckSquare].
success,
/// Warning semantic type — use `tokens.colors.warning` for cautionary messages.
///
/// Associated icon: [LayrzIcons.solarOutlineDangerSquare].
warning,
/// Danger semantic type — use `tokens.colors.danger` for destructive/critical messages.
///
/// Associated icon: [LayrzIcons.solarOutlineCloseSquare].
danger,
/// Contextual semantic type — use `tokens.colors.contextual` for context-dependent messages.
///
/// Associated icon: [LayrzIcons.solarOutlineMenuDotsSquare].
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 [LayrzIcons.solarOutlineInfoSquare] if
/// `color` and `icon` are null, respectively.
custom,
}| Type | Token Color | Icon | Use Case |
|---|---|---|---|
| info |
tokens.colors.info (blue) |
solarOutlineInfoSquare |
Neutral, informational messages |
| success |
tokens.colors.success (green) |
solarOutlineCheckSquare |
Confirmation, successful operations |
| warning |
tokens.colors.warning (orange) |
solarOutlineDangerSquare |
Cautionary, attention-needed messages |
| danger |
tokens.colors.danger (red) |
solarOutlineCloseSquare |
Critical, destructive, error messages |
| context | tokens.colors.contextual |
solarOutlineMenuDotsSquare |
Context-dependent, custom application logic |
| custom | Provided via color parameter |
Provided via icon parameter |
Any other semantic meaning |
Visual style classification that determines appearance:
enum LayrzAlertStyle {
/// Default Layrz style with neutral surface, severity-tinted border, and tinted icon chip.
layrz,
/// Tonal fill style with muted semantic color and matching text.
filledTonal,
/// Solid fill style with semantic color and contrasting text.
filled,
/// Outlined style with semantic border and matching text.
outlined,
/// Split-panel style with semantic color on left, neutral surface on right.
filledIcon,
}Each style resolves background, border, icon chip, title, and body colors independently. The table below shows how each style handles color rendering:
| Style | Background | Border | Icon Chip Background | Title Color | Body Color | Icon Color |
|---|---|---|---|---|---|---|
| layrz | surface |
Tonal accent | Tonal accent | fg1 |
fg2 |
Accent |
| filledTonal | Tonal accent | None | None | Accent | Accent | Accent |
| filled | Solid accent | Solid accent | None | Contrast | Contrast | Contrast |
| outlined | Transparent | Accent | None | Accent | Accent | Accent |
| filledIcon |
surface (right) |
None | Solid accent (left) | 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. It was newly specified for layrz_ui and represents the design system's preferred alert appearance:
- Background: neutral surface (white in light mode), matching the container it sits within
- Border: tonal accent (semi-transparent severity color), providing semantic emphasis without overwhelming
- Icon chip: tonal accent with circular background, positioning the semantic icon as a visual anchor
-
Text: neutral foreground colors (
fg1for title,fg2for body), ensuring strong readability
This style works universally on neutral backgrounds and is the safe choice for inline alerts in forms, cards, and dialogs.
A single-row container with:
- Left: circular icon chip (fixed size
kLayrzAlertIconBoxSize= 34.0 logical pixels) - Center: 12 logical pixels gap
- Right: text column with bold title and body description
┌────────────────────────────────────┐
│ ⊕ │ Title Bold │
│ │ Description text... │
└────────────────────────────────────┘
A row split into two panels:
-
Left panel: solid accent background with a larger centered icon (
kLayrzAlertFilledIconSize= 25.0 logical pixels) - Right panel: neutral surface background with title and description
┌────────────┬────────────────────────┐
│ ⊕ accent │ Title Bold │
│ color │ Description text... │
└────────────┴────────────────────────┘
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.titleMediumwithfontWeight.bold. -
description(String, required): Body text. Displays intokens.typography.bodyMedium. 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: Surface settles back down to its resting position; shadow steps down one level.
-
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.sp16 (16 logical pixels all sides) |
| Border radius |
tokens.radius.r12 (12 logical pixels) |
| Icon box size (standard styles) |
kLayrzAlertIconBoxSize = 34.0 logical pixels |
| Icon glyph size (standard styles) |
kLayrzAlertIconSize = 22.0 logical pixels |
Icon glyph size (filledIcon style) |
kLayrzAlertFilledIconSize = 25.0 logical pixels |
| Gap between icon and text |
tokens.spacing.sp12 (12 logical pixels) |
| Gap between title and description |
tokens.spacing.sp4 (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).
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
/// [LayrzIcons.solarOutlineInfoSquare]. 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: LayrzIcons.solarOutlineHeartLinear,
)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.filledTonal,
)LayrzAlert(
type: LayrzAlertType.warning,
title: 'Warning',
description: 'This action requires your confirmation. Please review carefully before proceeding.',
maxLines: 4,
style: LayrzAlertStyle.outlined,
)LayrzAlert(
type: LayrzAlertType.danger,
title: 'Error',
description: 'An unexpected error occurred. Please try again later.',
style: LayrzAlertStyle.filled,
)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: LayrzIcons.solarOutlineHeartLinear,
)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.filledTonal,
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 |
|---|---|---|
| Inside a form on neutral background |
.layrz (default) |
Tonal border and icon chip provide semantic emphasis without overwhelming |
| Inside a card or elevated container |
.filledTonal or .filledIcon
|
Solid fill ensures the alert stands out against the container background |
| Dismissible alert in a banner or overlay | .filled |
Solid color commands attention for critical messages |
| Inline hint or secondary message | .outlined |
Minimal visual weight, suitable for supplementary information |
| Split-screen or two-panel layout | .filledIcon |
Left panel with icon, right panel with text; strong visual separation |
-
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 -
layrz_icons (^2.0.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