naki_ui 1.0.0 copy "naki_ui: ^1.0.0" to clipboard
naki_ui: ^1.0.0 copied to clipboard

A library of Material-inspired UI components and design utilities for building responsive web applications with Jaspr.

Naki UI #

Naki UI is a Material-inspired library of 75+ reusable UI components and design utilities for building fast, responsive web applications with Jaspr.

Naki UI Logo

Documentation  β€’  Live Demo

Features #

  • 🎨 Design tokens and theming: Light, dark, and system theme modes, custom token overrides, and CSS variable management.
  • πŸ“¦ Component library: Layouts, accordions, cards, buttons, inputs, selection controls, overlays, navigation bars, and asynchronous builders.
  • πŸš€ Built for Jaspr: Jaspr components with type-safe CSS-in-Dart styling and DOM event binding.
  • 🧩 Modular barrel exports: Separate entry points for UI components, theming, and framework utilities.

Installation #

Add naki_ui to your pubspec.yaml:

dependencies:
  jaspr: ^0.23.4
  naki_ui: ^1.0.0

Then fetch the dependencies:

dart pub get

Agent skills #

Naki UI publishes consumer-facing agent skills with the package. You can globally activate naki_ui CLI using dart pub global activate naki_ui, then install skills as follows:

naki_ui skills --antigravity
naki_ui skills --cursor
naki_ui skills --claude-code

# Or specify with the --agent option:
naki_ui skills --agent antigravity

# Show help and all available options:
naki_ui --help
naki_ui -h
naki_ui skills --help

The package provides naki-ui-fundamentals, naki-ui-framework, and naki-ui-theming skills. The fundamentals skill includes a component index and detailed usage resources for every exported UI component.

naki_ui skills also automatically detects and installs companion Jaspr skills (jaspr-fundamentals, jaspr-styling, jaspr-convert-html, jaspr-pre-rendering-and-hydration, and jaspr-js-interop) if they are missing from your target agent directory. Use --no-jaspr to skip Jaspr skills installation.

View all supported AI agents & CLI options
Agent CLI Flag Option (--agent) Destination Directory
Antigravity --antigravity antigravity .agents/skills
Cursor --cursor cursor .cursor/skills
Claude Code --claude-code, --claude claude-code .claude/skills
Cline --cline cline .cline/skills
Codex --codex codex .agents/skills
GitHub Copilot --copilot copilot .github/skills
Command Code --command-code command-code .commandcode/skills
OpenCode --opencode opencode .opencode/skills
Continue --continue continue .continue/skills
Windsurf --windsurf windsurf .windsurf/skills
General --general, --generic general .agents/skills

Additional CLI Options:

  • --list / -l: List all available Naki UI skills and their descriptions.
  • --skill <name>: Install or remove only a specific skill by name (e.g. naki-ui-theming).
  • --target <path>: Custom destination directory for skills.
  • --all: Install skills for all detected agents in the workspace.
  • --force / -f: Force overwrite existing skills even if unchanged.
  • --dry-run: Preview changes without modifying the filesystem.
  • --clean / --remove: Remove installed Naki UI skills from the target agent directory.
  • --[no-]jaspr: Toggle automatic companion Jaspr skills installation (defaults to on).

Quick start #

Wrap your application in NakiApp, or use NakiApp.router for applications with multiple routes. It configures design-token injection, document metadata, routing, and the default theme. Use NakiThemeProvider in a descendant subtree when you need a localized theme override.

Important

Understanding @client hydration boundaries

  • When to use @client:

  • In Jaspr static or server mode, annotate the uppermost component of an interactive subtree, such as the root App, an interactive feature, or a single-page application.

  • A client boundary is required for browser-side interactivity, including DOM event listeners (onTap, onChange), interactive overlays (Dialog, Snackbar, BottomSheet, Drawer), controller hooks, dynamic theme switching (context.toggleTheme()), and single-page routing (NakiApp.router).

  • When not to use @client:

  • Static, content-first pages: Marketing pages, articles, documentation, and non-interactive layouts can render entirely on the server for a zero-JS payload and fast initial paint.

  • Descendants of an interactive subtree: Do not annotate a child when an ancestor already defines the client boundary. Jaspr hydrates the descendant tree from that boundary.

  • Server-only asynchronous data loaders: Components that fetch data before HTML delivery, such as AsyncStatelessComponent and AsyncBuilder, must remain server-only.

  • Components with non-serializable parameters: Constructor fields that cross the boundary must be serializable, such as primitives, lists, maps, or encodable classes. Do not pass functions or component instances across the boundary.

1. Application without routing (NakiApp) #

import 'package:jaspr/dom.dart';
import 'package:jaspr/jaspr.dart';
import 'package:naki_ui/framework.dart';
import 'package:naki_ui/naki_ui.dart';
import 'package:naki_ui/theme.dart';

@client // Enables client-side hydration and browser APIs.
class App extends StatelessComponent {
  const App({super.key});

  @override
  Component build(BuildContext context) {
    return NakiApp(
      // Theme configuration and local-storage persistence.
      themeMode: ThemeMode.system,
      cacheThemeMode: true,
      lightTheme: const LightThemeData(
        colorSeed: ColorSeed(primary: Color('#0f766e')),
      ),
      darkTheme: const DarkThemeData(
        colorSeed: ColorSeed(primary: Color('#14b8a6')),
      ),

      // SEO and document metadata.
      seo: const SEO(
        title: 'My Jaspr Application',
        description: 'A modern web application built with Jaspr and Naki UI.',
        keywords: ['jaspr', 'naki_ui', 'material', 'web'],
      ),
      favicon: 'https://example.com/favicon.png',

      home: Scaffold(
        appBar: const AppBar(titleText: 'Naki UI'),
        body: Align(
          alignment: Alignment.center,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            spacing: 16,
            children: [
              NakiText(
                'Welcome to Naki UI!',
                style: TextStyle(
                  fontSize: Dim.px(28),
                  fontWeight: FontWeight.bold,
                ),
              ),
              Button(
                onTap: () => print('Button tapped!'),
                child: const NakiText('Get Started'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

2. Application with routing (NakiApp.router) #

import 'package:jaspr/dom.dart';
import 'package:jaspr/jaspr.dart';
import 'package:jaspr_router/jaspr_router.dart';
import 'package:naki_ui/framework.dart';
import 'package:naki_ui/naki_ui.dart';
import 'package:naki_ui/theme.dart';

@client // Required for client-side single-page routing.
class RoutedApp extends StatelessComponent {
  const RoutedApp({super.key});

  @override
  Component build(BuildContext context) {
    return NakiApp.router(
      // Theme configuration and local-storage persistence.
      themeMode: ThemeMode.system,
      cacheThemeMode: true,
      lightTheme: const LightThemeData(
        colorSeed: ColorSeed(primary: Color('#0f766e')),
      ),
      darkTheme: const DarkThemeData(
        colorSeed: ColorSeed(primary: Color('#14b8a6')),
      ),

      // SEO configuration.
      seo: const SEO(
        title: 'My Routed App',
        description:
            'A single-page application powered by Jaspr Router and Naki UI.',
        keywords: ['jaspr', 'naki_ui', 'spa', 'router'],
      ),

      routes: [
        Route(
          path: '/',
          name: 'home',
          builder: (context, state) => const HomePage(),
        ),
        Route(
          path: '/settings',
          name: 'settings',
          builder: (context, state) => const SettingsPage(),
        ),
      ],
    );
  }
}

Library architecture and barrel imports #

Naki UI provides three focused entry points for predictable API discovery and clean code organization.

1. UI component barrel #

Import all reusable Naki UI components:

import 'package:naki_ui/naki_ui.dart';

This barrel includes Flutter-style compositional components such as NakiApp, Card, ExpansionPanelList, ExpansionPanel, ExpansionTile, Button, TextField, Scaffold, Column, Row, Dialog, Dropdown, Switch, and ListView.

2. Theme and styling barrel #

Import theme providers, theme configuration, styling models, text styles, and theme-related extensions:

import 'package:naki_ui/theme.dart';

This barrel includes NakiThemeProvider, ThemeConfig, EdgeInsets, and related styling utilities.

3. Framework and utilities barrel #

Import low-level framework tools, gesture recognizers, overlay controllers, animation curves, and extensions:

import 'package:naki_ui/framework.dart';

This barrel includes GestureRecognizer, OverlayController, DropdownItem, Curves, NakiDebounce, NakiStatelessMixin, NakiStatefulMixin, and more.


Core component categories #

Naki UI provides 75+ components grouped into intuitive categories:

πŸ“ Layout and structure

Build responsive web application layouts:

  • NakiApp, Scaffold, AppBar, Drawer, BottomNavigationBar
  • MediaQueryProvider, Align, Expanded, Flexible, SizedBox
  • ExpansionPanelList, ExpansionPanelList.radio, ExpansionTile
  • Card, Card.outlined, Card.filled
  • Column, Row, Container
  • Stack, Positioned, Wrap, Padding, Margin, AspectRatio, SafeArea
  • ColoredBox, RotatedBox

Built-in spacing for rows and columns: Column and Row support a spacing parameter (double) that applies a uniform CSS gap between children. Prefer Column(spacing: 16, children: [...]) or Row(spacing: 12, children: [...]) instead of inserting SizedBox components between equally spaced items.

πŸ”€ Basics and typography

Core text formatting, display, and interactive components:

  • NakiText, RichText, TextSpan, ComponentSpan, Heading, SubHeading
  • Bold, Italic, Underline, Strikethrough
  • Icon, Image, Button, GestureDetector, Spinner
πŸ“ Inputs and selection controls

Type-safe form components and selection controls:

  • TextField, SegmentedInput, FormBuilder, AutoCompleteField, Label, Calendar
  • Checkbox, RadioButton, Switch, Slider, Dropdown, DropdownItem
πŸ’¬ Overlays and feedback

Interactive modals, alerts, and tooltips:

  • Dialog, BottomSheet, Banner, Snackbar, Tooltip, Popover, OverlayController
πŸ“œ Scrolling and data views

Performance-optimized scrollable containers and data presentation:

  • ListView, GridView, SingleChildScrollView, PageView
  • CarouselView, StaggeredView, Table

ListView.builder and GridView.builder support deterministic, incremental rendering for large collections:

ListView.builder(
  itemCount: 1000,
  initialItemCount: 24,
  loadMoreItemCount: 24,
  itemBuilder: (context, index) => NakiText('Item $index'),
)

Set initialItemCount to enable lazy batches. Leaving it null preserves eager rendering for backward compatibility. Lazy builders need a bounded, scrollable viewport. Rendered items remain mounted, so this reduces initial HTML and build work but does not provide DOM-recycling virtualization. Paginate or use a dedicated recycler when the fully loaded collection would still be too large for the browser.

⚑ Asynchronous builders and progress

Handle dynamic data loading and loading state feedback:

  • NakiFutureBuilder, NakiStreamBuilder, Spinner
🎨 Painting and styling

Visual transformations and clipping utilities:

  • DecoratedBox, Opacity, Visibility, Transform
  • ClipRect, ClipOval, BackdropFilter

Server and static rendering #

The package uses universal_web for browser APIs shared by client and server code. In Jaspr server or static applications, place the uppermost interactive subtree inside an @client component so controls, overlays, controllers, and asynchronous builders are hydrated. Constructor parameters that cross the client boundary must remain serializable. Purely visual components can render on the server without a client boundary.

Automatic DOM IDs are allocated by the nearest NakiThemeProvider. Root and localized providers share a request-scoped, weakly owned registry, keeping IDs unique without retaining completed render contexts. Identical server and client trees therefore receive the same generated ID sequence during hydration. Supply an explicit id whenever another document fragment, label, or test needs a stable public identifier. Components use GlobalNodeKey internally for direct node access so generated IDs are not treated as imperative lookup handles.


Theming and mode switching #

NakiApp initializes theme configuration and design-token injection at the application root. Use the BuildContext extensions to switch modes, or use NakiThemeProvider to apply a localized theme override to a descendant subtree.

import 'package:jaspr/jaspr.dart';
import 'package:naki_ui/framework.dart';
import 'package:naki_ui/naki_ui.dart';
import 'package:naki_ui/theme.dart';

// 1. Theme switching with BuildContext extensions.
class ThemeToggleButton extends StatelessComponent {
  const ThemeToggleButton({super.key});

  @override
  Component build(BuildContext context) {
    final currentMode = context.themeMode;

    return Button(
      onTap: context.toggleTheme,
      child: NakiText('Toggle Theme (Current: ${currentMode.name})'),
    );
  }
}

// 2. Localized subtree override with NakiThemeProvider.
class DarkPreviewCard extends StatelessComponent {
  const DarkPreviewCard({super.key});

  @override
  Component build(BuildContext context) {
    return NakiThemeProvider(
      mode: ThemeMode.dark,
      builder: (context) => Card(
        child: NakiText(
          'Forced dark theme subtree',
          style: TextStyle(color: context.textColor),
        ),
      ),
    );
  }
}

BuildContext extensions for theme and token access:

  • context.toggleTheme(): Cycles through dark, light, and system modes.
  • context.setTheme(mode): Sets a specific ThemeMode.
  • context.themeMode: Returns the active ThemeMode.
  • context.themeTokens: Returns the active, context-local design tokens. Tokens.current is only the default source of CSS variable names and fallback values.
  • context.textColor, context.backgroundColor, context.surfaceColor, context.green, and context.red: Provide type-safe Color shortcuts for theme tokens directly on BuildContext.

Gesture handling and interaction #

Bind clicks, double-clicks, long presses, and pointer events to Jaspr components:

import 'package:jaspr/jaspr.dart';
import 'package:naki_ui/framework.dart';
import 'package:naki_ui/naki_ui.dart';

class InteractiveCard extends StatelessComponent {
  const InteractiveCard({super.key});

  @override
  Component build(BuildContext context) {
    final gestures = GestureRecognizer(
      onClick: (e) => print('Card clicked'),
      onDoubleClick: (e) => print('Card double clicked'),
      onLongPress: (e) => print('Card long pressed'),
    );

    return GestureDetector(
      gestures: gestures,
      semanticLabel: 'Open interaction details',
      child: const Container(
        child: NakiText('Interact with me'),
      ),
    );
  }
}

Segmented input and OTP entry #

Handle PINs, verification codes, and one-time passwords with autofocus, backspace navigation, paste handling, and completion callbacks:

import 'package:jaspr/jaspr.dart';
import 'package:naki_ui/framework.dart';
import 'package:naki_ui/naki_ui.dart';
import 'package:naki_ui/theme.dart';

class OtpVerificationForm extends StatelessComponent {
  const OtpVerificationForm({super.key});

  @override
  Component build(BuildContext context) {
    return SegmentedInput(
      id: 'otp-verification',
      length: 6,
      type: SegmentedInputType.number,
      decoration: const InputDecoration(
        labelText: 'Enter verification code',
        helperText: 'A six-digit code has been sent to your phone',
      ),
      onChanged: (code) => print('Typing code: $code'),
      onCompleted: (code) => print('Completed OTP code: $code'),
    );
  }
}

Responsive design and dimensions #

Use MediaQueryProvider to observe either the viewport or a specific component.

Observe the viewport #

import 'package:jaspr/jaspr.dart';
import 'package:naki_ui/naki_ui.dart';

@client
class ResponsiveLayout extends StatelessComponent {
  const ResponsiveLayout({super.key});

  @override
  Component build(BuildContext context) {
    return MediaQueryProvider(
      builder: (context) {
        final width = MediaQueryProvider.width(context) ?? 0;
        final isMobile = width < 768;

        return isMobile
            ? const Column(children: [NakiText('Mobile view (single column)')])
            : const Row(children: [NakiText('Desktop view (multiple columns)')]);
      },
    );
  }
}

Observe a component #

Set id to observe the provider's rendered wrapper instead of the viewport. Read its measurements with the *Of helpers:

import 'package:jaspr/jaspr.dart';
import 'package:naki_ui/naki_ui.dart';
import 'package:naki_ui/theme.dart';

@client
class ObservedCard extends StatelessComponent {
  const ObservedCard({super.key});

  @override
  Component build(BuildContext context) {
    return MediaQueryProvider(
      id: 'observed-card',
      builder: (context) {
        final width = MediaQueryProvider.widthOf(context);
        final height = MediaQueryProvider.heightOf(context);
        final orientation = MediaQueryProvider.orientationOf(context);

        String pixels(double? value) =>
            value == null ? 'Measuring…' : value.toPx;

        return Card(
          child: Column(
            spacing: 4,
            children: [
              NakiText('Width: ${pixels(width)}'),
              NakiText('Height: ${pixels(height)}'),
              NakiText('Orientation: ${orientation?.name ?? 'unknown'}'),
            ],
          ),
        );
      },
    );
  }
}

MediaQueryProvider applies the supplied id to its wrapper. Do not apply a different id to the component returned by builder.

MediaQueryProvider static helpers:

  • MediaQueryProvider.of(context): Returns the active MediaQueryData.
  • MediaQueryProvider.width(context): Returns the viewport width in pixels.
  • MediaQueryProvider.height(context): Returns the viewport height in pixels.
  • MediaQueryProvider.orientation(context): Returns the active Orientation.
  • MediaQueryProvider.widthOf(context), MediaQueryProvider.heightOf(context), and MediaQueryProvider.orientationOf(context): Return the dimensions and orientation of the observed component identified by id.

License #

This project is licensed under the MIT License. See LICENSE for details.

6
likes
160
points
132
downloads

Documentation

API reference

Publisher

verified publisherflutterful.dev

Weekly Downloads

A library of Material-inspired UI components and design utilities for building responsive web applications with Jaspr.

Homepage
Repository (GitHub)
View/report issues

Topics

#jaspr #jaspr-components #naki-ui #naki-components

License

MIT (license)

Dependencies

args, io, jaspr, jaspr_icons_pack, jaspr_router, path, universal_web, yaml

More

Packages that depend on naki_ui