NakedLink
Headless Flutter link with caller-owned navigation, accessible semantics, and observable interaction state
NakedLink is a dependency-free navigation primitive with no built-in styling
or launching policy. It supplies Link semantics and the correct keyboard
contract while the application owns routing, URL launching, and validation.
When to use this
- Navigation: Move to a route, document, website, or other destination
- Inline links: Compose navigation into text without button behavior
- Design systems: Render custom visuals from hover, focus, press, and disabled state
Use a button for an in-place action. A Link communicates that activation navigates to or references another destination.
The complete fixture is in the GitHub repository.
Basic implementation
import 'package:flutter/material.dart';
import 'package:naked_ui/naked_ui.dart';
Widget docsLink(BuildContext context) {
return NakedLink(
onPressed: () => Navigator.of(context).pushNamed('/docs'),
child: const Text('Documentation'),
builder: (context, state, child) {
return DecoratedBox(
decoration: BoxDecoration(
color: state.isHovered ? const Color(0xFFEFF6FF) : null,
border: Border.all(
color: state.isFocused
? const Color(0xFF2563EB)
: const Color(0x00000000),
),
),
child: child!,
);
},
);
}Navigation ownership
onPressed is the only application activation callback. NakedLink does not
call a launcher or choose a router, so applications can use Navigator, a
routing package, or an optional launcher dependency without adding that policy
to every consumer.
linkUrl is optional destination metadata exposed through Flutter semantics.
On Flutter web, a non-null semantics URL becomes an anchor href. Omit
linkUrl when onPressed performs navigation; otherwise one DOM activation
can call the callback and follow the anchor. Callers must validate destinations
before passing them to either API.
Modified primary clicks follow the ordinary onPressed path. If an application
needs browser-style open-in-new-tab behavior, its callback or an opt-in
anchor/launcher layer must implement that policy.
A Link is interactive only when enabled is true and onPressed is non-null.
Otherwise it has no Link role, destination URL, tap action, or focus stop.
Activation behavior
| Input | Behavior |
|---|---|
| Primary tap | Calls onPressed once |
| Enter or Numpad Enter | Calls onPressed once; held-key repeats are ignored |
| Semantic tap | Calls the same onPressed path |
| Space | Remains unclaimed so the page can scroll |
| Modified primary click | Calls onPressed; modifier policy is caller-owned |
| Secondary click | Remains unclaimed |
Accessible naming
Visible child text supplies the accessible name by default. A non-empty,
caller-localized semanticLabel replaces descendant naming semantics so the
Link is announced once. Null and whitespace-only labels preserve the visible
child name. Use semanticHint only when the result is not clear from the name.
Set excludeSemantics only when the surrounding application supplies an
equivalent accessible navigation path.
Constructor
const NakedLink({
super.key,
this.child,
this.builder,
this.onPressed,
this.linkUrl,
this.enabled = true,
this.focusNode,
this.autofocus = false,
this.mouseCursor,
this.enableFeedback = true,
this.onFocusChange,
this.onHoverChange,
this.onPressChange,
this.semanticLabel,
this.semanticHint,
this.excludeSemantics = false,
})Properties
onPressed → VoidCallback?
Performs caller-owned navigation. A null callback makes the Link inert.
linkUrl → Uri?
Optional destination metadata for assistive technologies. On web it also maps
to an anchor href while the Link is enabled.
child → Widget?
The visual content. Supply child, builder, or both.
builder → ValueWidgetBuilder<NakedLinkState>?
Builds visuals from an immutable state containing interaction flags and the
current linkUrl.
enabled → bool
Allows activation when onPressed is also non-null. Defaults to true.
focusNode → FocusNode?
Optional caller-owned focus node. NakedLink never disposes it.
semanticLabel → String?
Optional localized accessible-name override. Blank values are treated as absent.
semanticHint → String?
Optional localized description of a non-obvious navigation result.
excludeSemantics → bool
Hides the Link and its subtree from accessibility services. Defaults to
false.