-
Notifications
You must be signed in to change notification settings - Fork 0
LayrzScrollbar
A Material-free scrollbar built on RawScrollbar, providing visible feedback for scrollable content. Automatically installed by default in LayrzApp on all vertical scrollables. Thumb always visible and rounded; track hidden until hover.
Metadata
Mirrors: Scrollbar (Flutter SDK, Material-dependent)
Phase: M5 (Layout, navigation, feedback)
Domain: Scrolling
Primitive: RawScrollbar from package:flutter/widgets.dart
Status: Confirmed scope (D37).
LayrzScrollbar is a Material-free scrollbar component designed to provide visual feedback on scrollable content. Unlike Flutter's Scrollbar (which is Material-coupled), LayrzScrollbar uses only RawScrollbar and basic container primitives.
- Always visible thumb: The scroll thumb is always rendered, rounded, and easy to grab (minimum 48px tall)
- Hidden track until hover: The track is invisible unless the mouse hovers over the scrollbar area
-
Material-free: Uses
RawScrollbarfromwidgets.dart, not the MaterialScrollbar -
Installed by default:
LayrzAppinstallsLayrzScrollBehavior(which uses this scrollbar) by default on all vertical scrollables - Vertical only: Horizontal scrollables and touch platforms are exempt
When you use LayrzApp without an explicit scrollBehavior parameter, LayrzScrollBehavior is installed automatically. This means:
- All vertical scrollables (ListView, SingleChildScrollView, etc.) show a
LayrzScrollbar - Thumb is always visible (rounded, ~8px wide)
- Track appears on hover
- Horizontal scrollables do NOT get scrollbars
- Touch platforms (iOS, Android) do NOT get scrollbars (native scrollbar precedence)
This is a visible change from apps that previously had no scrollbars. Consuming apps accustomed to no scrollbar feedback will now see them.
To disable LayrzScrollbar on specific scrollables or app-wide, pass an explicit scrollBehavior:
LayrzApp(
// ... other properties
scrollBehavior: ScrollBehavior(),
// This disables LayrzScrollBehavior and all scrollbars
)ScrollConfiguration(
behavior: ScrollBehavior(),
child: ListView(
// This ListView will not show a scrollbar
children: [...],
),
)Scrollbars use theme tokens:
-
Thumb color:
LayrzTokens.colors.fg2(secondary foreground) -
Thumb radius:
LayrzTokens.radius.r1(4 logical pixels) - Thumb minimum height: 48px
- Track color: Transparent (invisible until hover)
-
Track hover color:
LayrzTokens.colors.sf3(nested surface tint)
Extends ScrollBehavior to apply LayrzScrollbar to all vertical scrollables.
class LayrzScrollBehavior extends ScrollBehavior {
/// Creates a scroll behavior with LayrzScrollbar applied to vertical scrollables.
const LayrzScrollBehavior();
@override
Widget buildScrollbar(
BuildContext context,
Widget child,
ScrollableDetails details,
) {
/// Only apply scrollbar to vertical scrollables on desktop platforms.
/// Touch and horizontal scrollables use native feedback.
if (details.direction != Axis.vertical) {
return child;
}
switch (details.controller.position.platform) {
case TargetPlatform.android:
case TargetPlatform.iOS:
return child; // Native scrollbar feedback
default:
return LayrzScrollbar(
controller: details.controller,
child: child,
);
}
}
}class LayrzScrollbar extends StatelessWidget {
/// The scroll controller that drives the thumb position.
final ScrollController controller;
/// The scrollable child widget.
final Widget child;
/// Creates a [LayrzScrollbar] around [child].
const LayrzScrollbar({
super.key,
required this.controller,
required this.child,
});
@override
Widget build(BuildContext context) {
return RawScrollbar(
controller: controller,
thickness: 8.0,
radius: const Radius.circular(4.0),
thumbColor: context.theme.tokens.colors.fg2,
trackColor: Colors.transparent,
trackBorderColor: Colors.transparent,
trackVisibility: false,
trackRadius: Radius.zero,
thumbVisibility: true,
minThumbLength: 48.0,
notificationPredicate: _defaultScrollNotificationPredicate,
isAlwaysShown: false,
showTrackOnHover: true,
child: child,
);
}
}No configuration needed; scrollbars appear automatically:
void main() {
runApp(
LayrzApp(
// ... other properties
// LayrzScrollBehavior is installed by default
home: MyHome(),
),
);
}
class MyHome extends StatelessWidget {
@override
Widget build(BuildContext context) {
return LayrzLayout(
// ... other properties
body: ListView(
children: [...], // This ListView will show a LayrzScrollbar
),
);
}
}LayrzScrollbar(
controller: _scrollController,
child: ListView(
controller: _scrollController,
children: [...],
),
)class MyScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ScrollConfiguration(
behavior: LayrzScrollBehavior(), // Explicit but redundant (default in LayrzApp)
child: ListView(
children: [...],
),
);
}
}class NoScrollbarSection extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ScrollConfiguration(
behavior: ScrollBehavior(), // Plain behavior, no scrollbar
child: SingleChildScrollView(
child: MyContent(),
),
);
}
}- Keyboard: Scrollbars are not keyboard-interactive; use arrow keys or Page Up/Down on the scrollable itself
- Screen readers: Scrollbars are not announced (native scrolling feedback is available via accessibility APIs)
-
Visual contrast: Thumb color (
fg2) meets WCAG AA minimum contrast against surfaces
| Platform | Behavior |
|---|---|
| Desktop (Linux, Windows, macOS) | LayrzScrollbar visible on vertical scrollables |
| Mobile (iOS, Android) | Native scroll feedback takes precedence; no LayrzScrollbar |
| Web (Wasm) | LayrzScrollbar visible on vertical scrollables |
If you were using Flutter's Material Scrollbar:
Before (Material):
Scrollbar(
controller: _scrollController,
child: ListView(/* ... */),
)After (layrz_ui, via LayrzApp default):
// No code change required; scrollbars appear automatically in LayrzApp
LayrzApp(
home: ListView(/* ... */),
)If you need explicit control:
LayrzScrollbar(
controller: _scrollController,
child: ListView(/* ... */),
)- Component Catalog (Scrollbar visibility across all components)
- Theming (Token customization for thumb colour and radius)
- Repo: D37 — LayrzLayout and LayrzScaffoldShell Scope
Last updated: 2026-08-19
Status: Implementation underway (M5)
Made with ❤️ by Golden M, Inc.
- LayrzTextInput
- LayrzSelectableAction
- LayrzSelectionToolbar
- LayrzTextSelectionControls
- LayrzSelectionMagnifier
- LayrzSelectionHandlePainter
- LayrzTextAreaInput
- LayrzComboBoxInput
- LayrzNumberInput
- LayrzPasswordInput
- LayrzCheckboxInput
- LayrzRadioInput
- LayrzSelectInput
- LayrzMultiSelectInput
- LayrzSearchInput
- LayrzDualListInput
- LayrzDurationInput