Hone UI

Introduction

React components with a familiar API, React Aria's accessibility and plain CSS.

Hone UI is a free, open-source React component library. It uses the same component names, props, sx prop and createTheme API as the popular React component library, so an existing app can switch by changing its imports.

It works differently under the hood in three ways:

  • No styling engine runs in the browser. A build plugin compiles sx to plain CSS.
  • React Aria Components handle behavior and accessibility.
  • Each component ships a guide written for AI coding tools. /llms.txt lists them, and each one is served at /ai/<Name>.md, such as /ai/Button.md.

Installation

Install the package and React Aria Components.

npm install @hone-ui/react react-aria-components
npm install -D @hone-ui/sx

The @hone-ui/sx plugin compiles sx to CSS. Set it up for your bundler.

Vite

Add the plugin before react(), and keep react() in the list so Fast Refresh still works.

vite.config.ts
import react from '@vitejs/plugin-react';
import sx from '@hone-ui/sx/vite';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [sx(), react()],
});

Delete the template's src/index.css and src/App.css, and their imports. Their unlayered rules beat the library's layered CSS. To keep them, put them in an earlier layer, as the theming page shows.

Import one look at your app's root, such as src/main.tsx.

import '@hone-ui/react/styles/material.css'; // or minimal.css

Next.js

Wrap your config with withSx. Pass it the template's existing options so they survive, and turn on optimizePackageImports. Without it, next build --webpack ships every component on every route. Turbopack does not need it.

next.config.ts
import { withSx } from '@hone-ui/sx/next';
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  // Keep the options create-next-app generated, such as cacheComponents.
  experimental: { optimizePackageImports: ['@hone-ui/react'] },
};

export default withSx(nextConfig);

Unlayered CSS beats every rule in the library's layers, and the compiled sx too. The app/globals.css file from create-next-app has an unlayered * { margin: 0; padding: 0 } reset and body rules. They collapse component spacing and leave the body white in dark mode. Delete the file and its import, or wrap its rules in a layer that ranks first:

app/globals.css
@layer reset, hone-ui;

@layer reset {
  * {
    margin: 0;
    padding: 0;
  }
}

The @layer reset, hone-ui; line works only if globals.css loads before the look, because the look's CSS declares the hone-ui layer the moment it loads. Import globals.css first in the layout.

app/layout.tsx
import './globals.css';
import '@hone-ui/react/styles/material.css'; // or minimal.css
import InitColorSchemeScript from '@hone-ui/react/InitColorSchemeScript';
import { AppTheme } from './theme';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <InitColorSchemeScript />
        <AppTheme>{children}</AppTheme>
      </body>
    </html>
  );
}

Button href renders a link that reloads the page by default. To navigate on the client, wrap the app once in React Aria's RouterProvider, inside your 'use client' theme file.

app/theme.tsx
'use client';

import { createTheme, ThemeProvider } from '@hone-ui/react/styles';
import { useRouter } from 'next/navigation';
import type { ReactNode } from 'react';
import { RouterProvider } from 'react-aria-components';

const theme = createTheme();

export function AppTheme({ children }: { children: ReactNode }) {
  const router = useRouter();
  return (
    <RouterProvider navigate={router.push}>
      <ThemeProvider theme={theme}>{children}</ThemeProvider>
    </RouterProvider>
  );
}

Ctrl-click and Cmd-click still open a new tab. In React Router, pass useNavigate() instead.

Theme provider

Wrap your app in ThemeProvider, and add CssBaseline for the page background and font.

import { CssBaseline, createTheme, ThemeProvider } from '@hone-ui/react';

const theme = createTheme();

export function App() {
  return (
    <ThemeProvider theme={theme}>
      <CssBaseline />
      {/* your app */}
    </ThemeProvider>
  );
}

In Next.js, put the provider in a file marked 'use client', as app/theme.tsx above does. The theming page covers themes in full.

Requirements

React 19 and react-aria-components 1.21 or later. The packages ship their own types and are built and tested with TypeScript 6.0. TypeScript 7.0.2 also compiled a sample app. The packages set no typescript peer range, so no version is enforced.

Canary builds

Each merge to main with pending changes publishes a canary under the canary tag, for example npm install @hone-ui/react@canary. With pnpm 10.16 or later, minimumReleaseAge can block a fresh canary because it is only minutes old. Exclude the hone-ui packages in pnpm-workspace.yaml:

minimumReleaseAgeExclude:
  - '@hone-ui/*'
  - hone-ui

Status

The library is in early development. It exports 75 components and parts, such as DialogTitle and TableRow, from Button and TextField to Dialog and Select. The components page has a guide with examples, a props table and notes for each main component.

Not included yet

Container, Grid, Link, ButtonGroup, ToggleButton, Breadcrumbs, Stepper, Rating, Popover, Modal, Fab and an icons package. Use Box, Stack and the sx prop for layout in the meantime.

On this page