brutalist_ui 0.0.1
brutalist_ui: ^0.0.1 copied to clipboard
A neobrutalist design library for Flutter, built entirely on Flutter's core widget primitives.
brutalist_ui #
A neobrutalist design library for Flutter, built entirely on package:flutter/widgets.dart.
No Material. No Cupertino. Thick hard borders, flat saturated fills, and solid offset shadows with no blur. Pressing a control translates it by its shadow offset and collapses the shadow, so the element presses into the page.

Why this exists #
Flutter is moving the Material and Cupertino design libraries out of the SDK and onto pub.dev as
material_ui and cupertino_ui (both published by the flutter.dev verified publisher, first
release 2026-07-21). The core framework keeps the raw primitives; design systems become ordinary
packages.
This package is built for that world. Its only Flutter imports are widgets.dart,
foundation.dart, services.dart, and gestures.dart, and it composes the SDK's headless
primitives — RawRadio, ToggleableStateMixin, FocusableActionDetector,
WidgetStatesController, EditableText, showGeneralDialog — the same way cupertino_ui does.
Nothing here breaks when the in-SDK material.dart and cupertino.dart are removed.
Install #
Not yet published. Depend on it by path or git:
dependencies:
brutalist_ui:
path: ../brutalist_ui
Use #
The barrel re-exports package:flutter/widgets.dart, so one import is enough:
import 'package:brutalist_ui/brutalist_ui.dart';
void main() => runApp(
NeoApp(
theme: const NeoThemeData(),
darkTheme: const NeoThemeData.dark(),
home: NeoScaffold(
appBar: const NeoAppBar(title: Text('NEOBRUTALIST')),
body: Center(
child: NeoButton(
onPressed: () {},
child: const Text('PRESS ME'),
),
),
),
),
);
Theming #
NeoThemeData holds five tokens: colors, typography, borderWidth, borderRadius, and
shadowOffset. brightness is derived from the color scheme rather than stored separately, so a
theme can't disagree with its own colors.
NeoApp(
theme: NeoThemeData(
colors: const NeoColorScheme.light().copyWith(primary: Color(0xFFB8FF4F)),
borderWidth: 4,
shadowOffset: const Offset(6, 6),
),
home: ...,
)
NeoTheme.of(context) returns the nearest theme, falling back to const NeoThemeData() so
components still work without an enclosing NeoApp.
Components #
| App & layout | NeoApp, NeoScaffold, NeoAppBar, NeoBottomNavBar, NeoPageRoute |
| Actions | NeoButton, NeoIconButton, NeoPressable |
| Selection | NeoCheckbox, NeoSwitch, NeoRadio, NeoChip, NeoDropdown |
| Input | NeoTextField, NeoTextFormField, NeoSlider |
| Feedback | NeoDialog + showNeoDialog, NeoBottomSheet + showNeoSheet, NeoTooltip, NeoProgressBar, NeoActivityIndicator |
| Surfaces | NeoBox, NeoCard, NeoListTile, NeoDivider, NeoBadge, NeoScrollbar |
| Text selection | NeoTextSelectionControls, NeoTextSelectionToolbar, NeoMagnifier |
| Tokens | NeoThemeData, NeoColorScheme, NeoTypography, NeoVariant, NeoIcon |
NeoVariant (primary, secondary, accent, success, warning, danger, neutral) is the
shared color vocabulary across buttons, chips, badges, and toggles.
NeoBox (static) and NeoPressable (interactive) are the two primitives every other component is
built from — reach for them to build your own.
NeoIcon draws its glyphs with CustomPaint, so the package needs no icon font and does not
require uses-material-design: true.
Accessibility #
Every interactive component reports the right semantics: buttons as buttons, NeoCheckbox with
checked state, NeoSwitch as toggled, NeoRadio through RawRadio's own semantics, and
NeoSlider with a value plus increase/decrease actions. Controls are keyboard-focusable and
activate on Enter/Space via FocusableActionDetector. Focus is shown by recoloring the hard
shadow, which avoids the layout shift an added outline would cause.
Example #
cd example && flutter run
The gallery covers every component and toggles light/dark at runtime.
Text selection #
NeoTextField supports the full selection stack: draggable square handles, a hard-edged
copy/cut/paste/select-all toolbar, and a magnifier — all built on TextSelectionControls,
TextSelectionGestureDetectorBuilder, and RawMagnifier.
Context-menu labels default to English because the package takes no dependency on
flutter_localizations or intl. To localize, pass labelFor to
NeoTextSelectionToolbar.buttonsFor from your own contextMenuBuilder:
NeoTextField(
contextMenuBuilder: (BuildContext context, EditableTextState state) {
final TextSelectionToolbarAnchors anchors = state.contextMenuAnchors;
return NeoTextSelectionToolbar(
anchorAbove: anchors.primaryAnchor,
anchorBelow: anchors.secondaryAnchor ?? anchors.primaryAnchor,
children: NeoTextSelectionToolbar.buttonsFor(
context,
state.contextMenuButtonItems,
labelFor: (ContextMenuButtonItem item) => myLocalizedLabel(item),
),
);
},
)
Known limitations #
- Context-menu and selection-toolbar labels are English by default (see above).
NeoRadiorequires aRadioGroup<T>ancestor (the current Flutter radio API).NeoScrollbarthumb has no border —RawScrollbarpaints a plain rect, so only the track carries the hard border.- No date/time picker, tabs, snackbar, or accordion yet — see the roadmap.
Roadmap #
Next, in rough order: NeoTabs, NeoSnackbar, NeoSegmentedControl, NeoAccordion,
NeoStepper, NeoAvatar, NeoSearchField, pull-to-refresh, grouped list sections, a skeleton
loader, and date/time pickers (which will introduce the flutter_localizations + intl
dependency).
Contributing #
Pull requests are welcome. Start with CONTRIBUTING.md — it explains the one hard
rule (no Material, no Cupertino in lib/, enforced by CI), how components are structured, and what
to run before opening a pull request.
By participating you agree to the Code of Conduct. To report a security issue, see SECURITY.md — please do not open a public issue for those.
License #
MIT — see LICENSE.
