Skip to content
EspañolEnglish

Getting started

There are three paths, from least to most work: a new application with create, an existing project with init, or setting it up by hand. The first two do the same as the third, and doctor checks the result in any of the three.

What you need

WhatVersion
React and React DOM^18.3.0 || ^19.0.0
Node.js>=20.19
Next (optional)^14.0.0 || ^15.0.0 || ^16.0.0
react-hook-form (optional)^7.54.0

And nothing else: the package has 0 runtime dependencies. react-hook-form is only needed for @amset/ui/form, and next for @amset/ui/next.

It is installed from the package registry of the AMSET GitLab: the public npm release is planned and not published yet. One line in the project's .npmrc says where the package comes from, and the read token goes in yours:

.npmrc
# the repository's .npmrc (committed)@amset:registry=https://gitlab.amset.tech/api/v4/packages/npm/ # ~/.npmrc, your own, outside the repository (never committed)//gitlab.amset.tech/api/v4/packages/npm/:_authToken=<token with read_package_registry>
  • A 401 does not mean "that package does not exist": it means the token is not valid. The registry answers 401 to everything that arrives without credentials.
  • A misspelt name does not give an error: whatever this registry does not have it forwards to the public npm, so @amset/iu would install a stranger's package.

A new application

Code
npx amset-ui create mi-app                       # Next with the App Routernpx amset-ui create mi-panel --template vite      # Vite

It comes with strict TypeScript, ESLint, the frame with its menu, one page per file, your theme and the provider already in place. No Tailwind.

Its options and what it writes, with the real output.
Whole screens to add to it: dashboard, customers, invoice, settings, sign in.

An existing project

Code
npx amset-ui init --brand "#2F9E44" --installnpx amset-ui doctor

init adds the registry line, the base stylesheet in the correct layer order, your theme and the provider with the Next router, without overwriting anything of yours. doctor checks that everything is in place and exits with 1 if something fails.

What it touches, file by file, with the before and after.
What it diagnoses and how to use it in CI.

By hand

Code
npm install @amset/ui

You do not need to import any stylesheet: each component brings its own, and the bundler only takes those of what you use. The library's stylesheets go in the @layer amset-ui layer, so any CSS of yours outside a layer beats them without !important. If you also import the whole stylesheet (@amset/ui/styles.css), you pay for the rules twice: it is one or the other.

The theme goes in data-theme="light" or "dark" on <html>; without the attribute the operating system decides, and printing always uses the light one. Density goes in data-density. Both are unprefixed: if your project already uses data-theme with other values, the library applies no theme.

Without a provider, the library speaks Spanish (es-ES). In an application in another language, use AmsetProvider with its locale: texts, dates, numbers and currency.

Next with the App Router

app/layout.tsx
// app/layout.tsx (server component)import { AmsetProvider, INITIAL_THEME_SCRIPT } from '@amset/ui'import { NextRouterProvider } from '@amset/ui/next' export default function Layout({ children }: { readonly children: React.ReactNode }) {  return (    <html lang="en" suppressHydrationWarning>      <head>        <script dangerouslySetInnerHTML={{ __html: INITIAL_THEME_SCRIPT }} />      </head>      <body>        <AmsetProvider locale="en-US">          <NextRouterProvider>{children}</NextRouterProvider>        </AmsetProvider>      </body>    </html>  )}
  • Links. Without a router, Link and ButtonLink navigate by reloading the whole page, without any error. NextRouterProvider routes them through router.push and prefetches them on hover or focus, like next/link.
  • Server and client. Components without interaction do not carry 'use client'; the rest, as far down as possible. None of them reads "today" or "now" while rendering, so the server HTML and the browser HTML match on hydration.
  • The theme without a flash: INITIAL_THEME_SCRIPT in the <head> applies the saved theme before the first paint.
  • SEO: @amset/ui/seo is a server subpath with no dependencies: pageMetadata (Next's Metadata), JsonLd and checkSeo.

Next with the Pages Router

Code
// next.config.tsexport default { transpilePackages: ['@amset/ui'] } // pages/_app.tsximport { AmsetProvider } from '@amset/ui'import { NextPagesRouterProvider } from '@amset/ui/next/pages' export default function App({ Component, pageProps }) {  return (    <AmsetProvider locale="en-US">      <NextPagesRouterProvider>        <Component {...pageProps} />      </NextPagesRouterProvider>    </AmsetProvider>  )}

With the Pages Router you need transpilePackages: ['@amset/ui']: without it, Next loads the package with Node, which does not know what to do with the .css files each component imports, and the build fails. The App Router, Vite and webpack need nothing.

Vite (and any other router)

Code
// src/main.tsx, with React Routerimport { AmsetProvider, RouterProvider } from '@amset/ui'import { useNavigate } from 'react-router' function Enrutado({ children }: { readonly children: React.ReactNode }) {  const navigate = useNavigate()  return <RouterProvider navigate={(href) => { void navigate(href) }}>{children}</RouterProvider>}

RouterProvider receives the function your router navigates with. With React Router it is navigate; with another, whichever it uses. Without it, links reload the page.

React 18 and 19

It works with React ^18.3.0 || ^19.0.0, and both versions are tested: besides the suite with React 19, CI renders every component page with real React 18 and fails on any React exception or warning.

The only noticeable difference is the ref: in React 18 a function component does not receive it as a prop. The three that expose it in their type (Button, IconButton and FloatingButton) are wrapped so that it arrives the same way in both; in the rest, the inner element is requested with inputRef or similar.

With Tailwind

With Tailwind 4, or without Tailwind, there is nothing to do. Tailwind 3 does not emit native layers: its preflight and its utilities beat any layer, whatever the order of the imports, and the button loses its background and the field gets the border from @tailwindcss/forms. The fix is to put the preflight below the library and the utilities above it, compiling Tailwind into two files:

globals.css
/* globals.css, with Tailwind 3 */@layer tw-base, amset-ui, tw-utils; @import './tailwind-base.css' layer(tw-base);   /* only @tailwind base */@import './tailwind-utils.css' layer(tw-utils); /* @tailwind components and utilities */

Languages

The library's texts (accessible labels, messages, screen reader announcements) come in Spanish and English. Any other language is loaded from the provider as a JSON of "Componente.clave": "texto"; the template with every key is @amset/ui/locales/en.json.

Code
import { AmsetProvider } from '@amset/ui' const catalogs = {  fr: () => import('./i18n/fr.json'),   // only downloaded if the locale is "fr"  'pt-BR': () => import('./i18n/pt-BR.json'),} <AmsetProvider locale={locale} catalogs={catalogs}>{children}</AmsetProvider>

They are loaded on demand: with seven languages configured, the browser downloads one. To render it translated on the server, loadMessages(locale, catalogs) resolves it beforehand and it is passed as messages.

Testing with Jest

Jest needs a moduleNameMapper that turns .css files into an empty module, because each component imports its stylesheet. Vitest already does this on its own.

jest.config.js
// jest.config.jsexport default {  moduleNameMapper: { '\\.css$': '<rootDir>/test/empty-css.js' },}