--- url: https://adecore.dev/guide.md --- # Introduction This repository holds the packages that desktop apps for working with agents share, so the parts they have in common are written once. ## Packages ### Interface | Package | What it holds | |---|---| | [UI](/ui/) (`@adecore/ui`) | React components, a theme, formatters and a settings dialog | | [Terminal](/terminal/) (`@adecore/terminal`) | A terminal pane on xterm.js in the colors of the theme, fed by the app through a handle | ### Desktop | Package | What it holds | |---|---| | [Shell](/shell/) (`@adecore/shell`) | The main process of an Electron app: its menu, its updater, its windows and the guards around its page | | [Service](/service/) (`@adecore/service`) | Definitions and managers for a launchd job on macOS or a systemd user service on Linux | ### Agents | Package | What it holds | |---|---| | [Agent contracts](/agent-contracts/) (`@adecore/agent-contracts`) | Schemas and port contracts for agent chats, provider accounts, models and usage | | [Agents](/agents/) (`@adecore/agents`) | Chat hosts for Node and Bun over provider CLIs, with accounts, usage and task coordination | | [Agent views](/agents-react/) (`@adecore/agents-react`) | React views for agent chats: threads, the composer, approvals, provider accounts and usage | | [Intelligent UI](/intelligent-ui/) (`@adecore/intelligent-ui`) | The component catalog, compiler and bounded interpreter for blocks of UI in an agent's reply | ### Editor | Package | What it holds | |---|---| | [Editor core](/editor-core/) (`@adecore/editor-core`) | The document model: text, selections, undo history, search, folding and editing commands, with no DOM | | [Editor](/editor/) (`@adecore/editor`) | The DOM view that draws a document model | | [Editor views](/editor-react/) (`@adecore/editor-react`) | React editors with language features, popups and change review over an injected language service | | [LSP](/lsp/) (`@adecore/lsp`) | A Language Server Protocol client with no DOM, and the language service interface the editor asks | | [Merge](/merge/) (`@adecore/merge`) | Line diffs, three-way merge blocks and conflict resolution for text | ### Data | Package | What it holds | |---|---| | [Database](/database/) (`@adecore/database`) | Views to browse, query and edit SQLite and MySQL databases, a host for the backend and a native helper | ### Canvas | Package | What it holds | |---|---| | [Drawing](/drawing/) (`@adecore/drawing`) | Drawing schemas, geometry, paths, SVG export and reading order | | [Diagram](/diagram/) (`@adecore/diagram`) | Directed graphs on top of drawing, with layered layout and SVG export | | [Plan](/plan/) (`@adecore/plan`) | Plan trees with atomic operations, permissions for people and agents, and Markdown | ## One version Every package is released under the same version, from one tag. Take them together: a version of one package is tested against the same version of the others. While the version is `0.x`, a breaking change bumps the minor and everything else the patch. The release notes on GitHub name each breaking change with what to do about it. ## Installing Every package comes from npm and is installed on its own: ```sh bun add @adecore/ui ``` The page of a package says which peer dependencies it expects and how to wire it into an app. ## Working on a local checkout Every package has a `source` export condition that points into its `src`, so an app can use a checkout without building it after every change. Link the package from its own folder: ```sh # in packages/ of the adecore checkout bun link # in your app bun link @adecore/ ``` Then turn the condition on wherever the app resolves modules: `resolve.conditions` in Vite, `customConditions` in TypeScript and `--conditions=source` for `bun test`. A package's own page lists what else it needs, such as a single copy of React. ## The React Compiler The packages with React views (UI, Terminal, Agent views and Editor views) are built with the [React Compiler](https://react.dev/learn/react-compiler), so what npm serves from `dist` memoizes its components and hooks itself: a chat that streams or a composer that is typed in draws only the parts that changed. The compiled code imports `react/compiler-runtime`, which comes with React 19. The `source` condition gives an app the TypeScript as it is written. To keep the memoization, run the compiler over those sources the way the app runs it over its own: `babel-plugin-react-compiler` with `target: '19'`, through `reactCompilerPreset` of `@vitejs/plugin-react` in Vite. A bundler setup that leaves `node_modules` out has to let the `src` of these packages in. Without the compiler every view still works, but the sources hold no `useMemo` or `useCallback` the compiler covers: a view draws again as often as its parent does and works out what it shows anew each time. A production build that drops the `source` condition takes the compiled `dist` instead. A function that starts with `'use no memo'` stays as it is written, in `dist` too, and so does one the compiler cannot prove safe. --- --- url: https://adecore.dev/ui.md --- # @adecore/ui Components for desktop-grade React apps: menus, dialogs, tooltips, fields, toasts, a settings dialog, keyboard shortcuts and formatters, drawn from one theme. Built on React 19, [Base UI](https://base-ui.com), [Lucide](https://lucide.dev) and Tailwind 4. ```sh bun add @adecore/ui ``` [Getting started](/ui/guide/getting-started) wires it into an app. [Principles](/ui/guide/principles) explains the rules every component follows. | Entry point | What it holds | | --- | --- | | `@adecore/ui` | The components, hooks and helpers. | | `@adecore/ui/settings` | The [settings dialog](/ui/settings/settings-dialog) and the parts of a pane. | | `@adecore/ui/format` | The [formatters](/ui/formatting/) for numbers, dates and durations. | | `@adecore/ui/testing` | [Fakes](/ui/utilities/testing) for your own tests, and `/testing/dedupe`, a `bun test` preload. | | `@adecore/ui/theme.css` | The [theme](/ui/guide/theme). | ## What it gives you ### Base UI underneath Menus, dialogs, popovers, selects and tooltips are Base UI parts with the library's look on them, so focus, arrow keys, typeahead and Escape already work. ### One theme of tokens Every color is a semantic token with a light and a dark value. Tailwind's palette is gone, so a component can only reach the theme. Your app sets its own accent. ### Shortcuts written once One value decides whether a key event is the shortcut and how it prints, as ⌘K on a Mac and Ctrl+K elsewhere. With [`ShortcutHints`](/ui/display/shortcut-hints) mounted, holding the modifier shows every visible button's key. ### Formatters that respect the region Numbers, dates and durations follow the language a person reads and the region they picked, separately. English words in a Dutch notation is a pair people set. ### A settings dialog Sections on the left, a pane on the right, search that jumps to a row, and parts for rows, cards and master-detail panes. ### English and Dutch The library's own words live in a `ui` namespace that [`UIProvider`](/ui/utilities/ui-provider) adds to your i18next. English is the source, and Dutch has every key. --- --- url: https://adecore.dev/ui/guide/getting-started.md --- # Getting started `@adecore/ui` is a set of React components for apps that behave like desktop software: dense toolbars, menus with shortcuts, dialogs over dialogs, a settings window. It sits on [Base UI](https://base-ui.com) for behavior, [Lucide](https://lucide.dev) for icons and Tailwind 4 for styling, and it ships its own theme. ## Install ::: code-group ```sh [bun] bun add @adecore/ui ``` ```sh [npm] npm install @adecore/ui ``` ```sh [pnpm] pnpm add @adecore/ui ``` ::: React 19, `react-dom`, `i18next` and `react-i18next` are peer dependencies, so your app brings them. Base UI, Lucide, `clsx`, `zustand` and `@pierre/trees`, the engine of the file tree, come along with the library. ## Import the theme Import the theme right after Tailwind, then tell Tailwind to scan the library for the classes its components use: ```css @import "tailwindcss"; @import "@adecore/ui/theme.css"; @source "../node_modules/@adecore/ui/dist"; ``` The `@source` path is relative to the CSS file it sits in. Without it Tailwind never sees the classes inside the library, and the components render unstyled. The theme holds the semantic tokens (`bg-surface`, `text-text-muted`, `border-border` and the rest), the type scale and the rules utilities cannot express, such as the states of a menu row. It also resets Tailwind's color palette, so `bg-red-500` stops existing and every color comes from a token. Light and dark follow `data-theme="light"` or `data-theme="dark"` on ``. The [theme page](/ui/guide/theme) lists every token and shows how to set your own accent. ## Wrap the app in UIProvider Mount `UIProvider` once, above the first component of the library: ```tsx import i18next from 'i18next'; import { createRoot } from 'react-dom/client'; import { UIProvider } from '@adecore/ui'; createRoot(document.getElementById('root')!).render( ); ``` It adds the library's words to your i18next instance and hands the formatters your `formatSource`. It mounts the shared tooltip provider, so moving along a row of buttons shows each tooltip without a new delay. And it writes whether the keyboard or the pointer was used last, as `data-modality` on ``. An app that wires the format source itself calls `setFormatSource`. See [UIProvider](/ui/utilities/ui-provider). ## Words and languages The library's own words, such as "Cancel", "Dismiss" and "Zoom in", live in the `ui` namespace (`UI_NAMESPACE`). English and Dutch ship with it. `UIProvider` adds both to the i18next instance you pass it, next to your own namespaces, and a component reads them through `useTranslation('ui')` on that instance. Switch the language the way you already do, with `i18n.changeLanguage('nl')`. A language you filled yourself keeps its words, since `UIProvider` skips a language that already has a `ui` bundle. To translate the library into a third language, add a `ui` bundle for it before the provider mounts. `UI_RESOURCES` holds the English source to translate from. ## The format source The formatters in `@adecore/ui/format` write every number, date and duration a person reads. They need two settings: the language, which writes the words (`Sep` or `sep`), and the region, which writes the order, the separators and the clock (`9/19/2026, 8:05 AM` or `19-9-2026 08:05`). The two are separate on purpose. English with a Dutch notation is a common pair on a Dutch computer. A `FormatSource` hands both over: ```ts import { FORMAT_LANGUAGE, type FormatSource } from '@adecore/ui/format'; const formatSource: FormatSource = { language: () => settings.language, // A tag such as `nl-NL`, or FORMAT_LANGUAGE to follow the language, or FORMAT_SYSTEM. region: () => settings.region ?? FORMAT_LANGUAGE, // What a desktop shell reads from the operating system; a browser leaves it out. systemLocale: () => desktopShell?.systemLocale, subscribe: (onChange) => settings.subscribe(onChange) }; ``` Without one, the formatters write English. [Format source](/ui/formatting/) explains how the region resolves. ## Optional pieces Mount [``](/ui/display/shortcut-hints) once if holding Cmd (Ctrl on Windows and Linux) should print every visible button's shortcut under it; it listens on the window. Mount [``](/ui/overlays/toasts) once for a stack of toasts from `createToastStore()`. ## Working on a local checkout The package has a `source` export condition that points into `src`. An app can use a checkout of this repository without building it after every change. Link the checkout: ```sh # in packages/ui of the checkout bun link # in your app bun link @adecore/ui ``` A `file:../adecore/packages/ui` dependency works too, but Bun copies the checkout on install, so a change reaches the app only after the next `bun install`. Turn the condition on in Vite, keep one copy of each shared dependency, and let Vite compile the library's source like your own: ```ts import { defaultClientConditions, defineConfig } from 'vite'; export default defineConfig({ resolve: { conditions: ['source', ...defaultClientConditions], // The checkout has its own node_modules; a second React or i18next breaks every hook and every word. dedupe: ['react', 'react-dom', 'i18next', 'react-i18next', '@base-ui/react'] }, optimizeDeps: { exclude: ['@adecore/ui'], include: ['@base-ui/react/menu', '@base-ui/react/dialog', 'lucide-react', 'clsx'] } }); ``` TypeScript needs the same condition, or it looks for types in a `dist` that may not exist: ```json { "compilerOptions": { "customConditions": ["source"] } } ``` Your app then type-checks the library's source with its own settings, so it needs `allowImportingTsExtensions`, `resolveJsonModule` and `"jsx": "react-jsx"`. Point Tailwind at the checkout's `src` instead of `dist`, again relative to the CSS file: ```css @source "../../adecore/packages/ui/src"; ``` The theme import stays the same. It resolves through the `style` condition, which points at `src/theme.css` in a checkout and in the published package. If Vite refuses to serve files from the checkout, add its folder to `server.fs.allow`. `bun test` takes the condition as a flag (`bun test --conditions=source`) and has no `dedupe`; the library ships a preload for that, see [Testing](/ui/utilities/testing#a-linked-checkout-under-bun-test). These docs use the same condition: every demo on this site draws the source in the repository, not a published build. --- --- url: https://adecore.dev/ui/guide/principles.md --- # Principles The design rules the library holds itself to. Most of them are checked by its own test suite, which reads every source file and fails on a violation. Code you write on top of the library fits in when it keeps them too. ## Type Interface text comes in four sizes: `text-xs`, `text-sm`, `text-base` and `text-lg`. Most of the interface is written in `xs`, so that is where reading has to work, and the steps above it are small. The scale also has `text-4xl` for a large figure and `text-code`, a fixed 13px that sits next to a terminal without scaling twice. No size is written in brackets (`text-[13px]`) and nothing goes below 12px. Every size is a `rem` rounded to a whole pixel, so text and the rem-based layout scale together when an app changes the root font size. Labels are sentence case and never `uppercase`. ## Hints are tooltips A hint is a [`Tooltip`](/ui/overlays/tooltip), never a `title` attribute. A `title` shows after a long delay, in the operating system's style, never on keyboard focus, and cannot carry a shortcut. A `Tooltip` opens on hover and on focus, shares one delay across the app, and prints its shortcut the way the platform writes it. An icon-only button needs an accessible name anyway. [`IconButton`](/ui/actions/icon-button) takes a `label` that is both the name and the tooltip, so the two never say different things. ## Borders are alpha A border is black or white at a low alpha, never a gray of its own (`--border`, `--border-strong`, `--border-soft`). A gray line drawn over a tinted surface reads as a smudge. The same line as an alpha takes the color of whatever it crosses and stays a line. That only works if the line mixes with the ground behind the element, so every surface clips its background to the padding box. The theme's base layer sets `background-clip: padding-box` on every element. A utility such as `bg-clip-text` still wins, because utilities sit in a later layer. ## Whole pixels Lengths land on whole pixels. The theme rounds every `rem` and `em` with `round(..., 1px)`, including the spacing unit, so `p-2` is twice a whole pixel. Line heights are rounded too, because a line height relative to the font puts a line of text between two pixels. Nothing in the library writes a fractional pixel. ## Icons are 12, 14, 16 or 20 An icon is 12, 14, 16 or 20 pixels and nothing between. [`Icon`](/ui/display/icon) draws Lucide glyphs at a stroke of 1.75, a hair thinner than Lucide's default, so a 12 or 14 pixel glyph does not outweigh the words beside it. An icon button decides the icon inside it from its own size: 16 in the default 32 pixel square, 14 in 28, and 12 in 24 and 20. It never takes a height, a width or a radius from a class. The size is a prop. ## Every shortcut needs a modifier A shortcut is Cmd or Ctrl plus a key, not a bare letter. A bare key belongs to whatever has the focus: a text field, a terminal, a canvas tool. The library writes a shortcut once with [`shortcut('Mod+K')`](/ui/utilities/shortcuts). `Mod` is Cmd on macOS and Ctrl elsewhere, and the same value both matches the key event and prints the hint. The library binds no shortcut on the window. The listeners it puts there only watch: [`ShortcutHints`](/ui/display/shortcut-hints) waits for the modifier held on its own, the input modality notes which device is in use, and a [`DockShell`](/ui/layout/dock-shell) with `autoHide` follows the pointer. Bind your app's shortcuts on the window once, in one place, rather than per component. ## Keyboard and focus Everything that acts takes the keyboard. The overlays inherit Base UI's behavior: * Menus and selects move with the arrow keys, jump with Home and End, and find a row by typing its first letters. * Enter and Space pick a row, Escape closes, and focus returns to the trigger. * A dialog traps focus while it is open, and a dialog opened over another stacks over it. * Radio groups such as [`ChoiceCards`](/ui/inputs/choice-cards) move between choices with the arrows. Keyboard focus is a 2px outline in the accent color, never a ring or a colored border. Pointer focus stays quiet. `.focus-ring` gives the same outline to something that takes focus without being a button, such as a row in a list, and `.focus-ring-within` draws it on a card that holds a focused field. ## Input modality A browser keeps `:focus-visible` on for scripted focus as long as its own keyboard flag is up. Base UI moves focus to the menu row under the pointer, so after a single keystroke anywhere, the focus outline would follow the mouse through every menu. The library decides by the last real input instead. `UIProvider` writes `data-modality="keyboard"` or `data-modality="pointer"` on `` from the last key or pointer event. Under the pointer, a highlighted menu row shows a lighter hover background and no outline. Under the keyboard it shows the stronger pressed background, a mark you can find. Style your own lists the same way with `.cursor-row[data-active='true']`, which follows the same two states. ## Numbers and dates Only `@adecore/ui/format` builds an `Intl` formatter. Every number, date and duration a person reads goes through it, so it follows the language and the region they set, and a list of a thousand rows builds no new formatter. See [Formatting](/ui/formatting/). ## Props follow Base UI A controlled value is `value` and `onValueChange`, `checked` and `onCheckedChange`, or `open` and `onOpenChange`. Variants and sizes are props, never class strings you pass in. Every component that draws an element takes `className` and a `ref`. The parts that are a single element also take Base UI's `render` prop, which swaps the element for another and merges the props of both: ```tsx }>Recent }>Readme ``` A component's own utilities and yours never set the same property. Tailwind does not choose between two utilities by their order in `class`, so where you need another value than the one a component draws, it is a prop. --- --- url: https://adecore.dev/ui/guide/theme.md --- # Theme `@adecore/ui/theme.css` holds the tokens every component draws with. Each color token has a light and a dark value, switched by `data-theme="light"` or `data-theme="dark"` on ``. The tables on this page are read from that file when the site builds, so they list exactly what it holds. Each swatch is drawn inside its own theme, which is why a token such as `--accent-soft`, mixed from other tokens, shows its real value in both columns. The theme maps the tokens onto Tailwind utilities (`bg-surface`, `text-text-muted`, `border-border`, `shadow-float`) and clears Tailwind's default palette with `--color-*: initial`. A component can reach only these colors, and so can your app, until you add your own. ## Ground and surfaces `--bg` is the page behind everything. A panel or a card is a `--surface`, and a popup, a menu or a dialog floats on `--surface-raised`. `--surface-sunken` is the recessed track of a segmented control or a field that should not draw a box. Hover and pressed get a step each, `--surface-hover` lighter than `--surface-active`, so pressed always outranks hover. `--field` is what a person types or picks in (a field, a select, a checkbox): a step lighter than the panel in the dark theme. `--thumb` is the knob of a slider or a switch: white with an edge in the light theme, light gray in the dark theme, so it stands apart from its track in both. ## Borders Alpha over whatever is behind, never a gray of their own (see [Principles](/ui/guide/principles#borders-are-alpha)). `--border-soft` is the hairline between rows, `--border-strong` the outline of a control that has to read as one. `--separator` is opaque and fills the space between separated panels. A layout that needs its borders to match those gaps can opt into `--border: var(--separator)` in both theme scopes. The default border tokens keep their alpha values. ## Text ## Accent The accent marks the primary button, the focus outline, a picked choice and the text selection. The default is a neutral blue; your app sets its own ([below](#your-own-accent)). ## Status Four states something can be in (running, waiting on a person, idle, failed) and a positive fill for a step that went well. ## Shadows `--float-shadow` lifts everything that floats: menus, dialogs, tooltips, toasts. `--raised-shadow` is the small lift of a picked segment off its track, and `--thumb-shadow` the edge and lift of a thumb. ## Media A control drawn over a picture rather than over the interface, such as the handle of a [`Wipe`](/ui/layout/wipe). It stays the same in both themes. ## Chart series The colors of the series of a chart, in order: the accent first, then orange, aqua, yellow, magenta and green, an order checked for color vision deficiency on both grounds. A series without a tone of its own takes the next one, as in [`@adecore/charts`](/charts/). ## File icons The file type palette, which [`FileIcon`](/ui/display/file-icon) colors a file type with. A file icon is the one thing the theme does not tint: TypeScript stays blue wherever the file shows up. ## Layers Whole numbers in one place, so a new popup cannot guess where it goes. A dialog sits under the menus and selects it can open, tooltips sit over both, and the [shortcut hints](/ui/display/shortcut-hints) over everything. A component reaches the popup step with `z-(--z-popup)`. ## Type scale The sizes are rem, rounded to whole pixels, against the font size your app sets on ``. 15px reads well in a desktop app. Code keeps an absolute size. `2xs` (12px at a 15px root) is only for a small label inside a row of `xs`, such as a [`Pill`](/ui/actions/pill). Nothing in the theme goes below it. ## Radius ## Code Code a person reads as code, such as an editor, a grid of database values or a statement, comes in one size apart from the type scale. `--code-font-size` and `--code-line-height` are `--text-code` and its line height until your app sets them on ``, for instance from a font size setting of its code editor; every view that draws code follows at once. Utilities reach them as `text-(length:--code-font-size)` and `leading-(--code-line-height)`. ## Spacing and fonts The spacing unit is rounded, not each multiple of it, so every step lands on a whole pixel. `--font-mono` is a variable your app can override on `` when a person picks a monospace font, and `font-mono` follows. ## Your own accent Set the accent in a rule after the theme import, in both forms. The second is the same color as channels, for the tokens that carry the accent with an alpha, such as the text selection. ```css @import "tailwindcss"; @import "@adecore/ui/theme.css"; :root { --accent: #7c3aed; --accent-rgb: 124 58 237; } ``` `--accent-soft` is mixed from `--accent` and follows by itself. Set `--accent-text` too if white does not read on your accent. To give the dark theme its own accent, repeat the rule under `[data-theme="dark"]`. ## Your own colors Add a token in both themes and map it onto a Tailwind color, the way the theme maps its own: ```css :root, [data-theme="light"] { --brand: #0f766e; } [data-theme="dark"] { --brand: #2dd4bf; } @theme inline { --color-brand: var(--brand); } ``` Now `bg-brand`, `text-brand` and `border-brand/40` exist. `inline` makes the utility read the variable where it is used, so the value follows `data-theme`. A token derived with `var()` resolves on the element that declares it; declare it in the same rule as the tokens it reads, as the theme does, so it follows a theme change. ## Rules the utilities cannot write The theme also carries a few classes for what a utility cannot express: the states Base UI puts on a menu row, pseudo-elements, keyframes. The components use them, and you can too, for something that has to look like one of them. | Class | What it draws | | --- | --- | | `.icon-btn`, `.icon-btn-sm`, `.icon-btn-xs`, `.icon-btn-2xs` | The square of an icon button and its pressed and disabled states. | | `.field`, `.field-sm` | A text field's border, height and focus outline. | | `.menu-popup`, `.picker-popup`, `.menu-item` | A menu's surface and a row in it, with its highlighted and disabled states. | | `.cursor-row` | A row of a list the arrow keys walk that is not a Base UI menu, with `data-active="true"` on the current one. | | `.dialog-popup`, `.dialog-backdrop`, `.lightbox-frame`, `.lightbox-backdrop` | A dialog's surface and dim, and a dialog that shows only a picture. | | `.tooltip-positioner`, `.tooltip-popup` | A tooltip that fades in from the side of its trigger. | | `.focus-ring`, `.focus-ring-within` | The keyboard focus outline on something that is not a button, or on a card around a focused field. | | `.scroll-fade-x`, `.scroll-fade-top` | A fade at an edge that still hides content, switched on by `data-fade-start` and `data-fade-end`. `.scroll-fade-x` takes its width from `--fade-size`. | | `.file-icon` | A file icon outside the tree: the hue the sprite gives its file type, or the muted text color. | Every class sits in Tailwind's `components` layer, so a utility on the same element still wins. --- --- url: https://adecore.dev/ui/actions/button.md --- # Button Every button with a word in it. A button that is only an icon is an [`IconButton`](/ui/actions/icon-button). ```tsx import { Button } from '@adecore/ui'; ``` ## Variants `ghost` is the default, a quiet button that lifts under the pointer. `secondary` has a border and a raised ground. Give a screen one `primary` at most. `danger` is for a deletion that cannot come back, `danger-outline` for a step that forgets something rather than destroys it, and `positive` for a step that completes something, such as a merge. `inverse` is dark on a light theme and light on a dark one, the way many sign-in buttons ask to be drawn. ## Sizes and links `md` is 32 pixels tall, `sm` 28 and `xs` 24, which fits a header of 32. An icon before the word is a 14 pixel [`Icon`](/ui/display/icon). With `href` the button is an anchor that opens in a new tab. ## Pill `shape="pill"` is 36 pixels tall and fully rounded, with text at the base size. It is for a button that sits in a round row of controls and takes the place of `size`, so `size` has no effect on it. ## Props `Button` takes every prop of a ` ``` ## Beside the control In a dialog with several fields, a stack of labels and controls soon scrolls. `orientation="horizontal"` puts the label in a column of 112 pixels beside the control, at the size of what is typed, so the controls of every field line up. The hint and the error stay under the control. The label sits on the middle of the control's first line, however tall a `TextArea` grows. ## Props | Prop | Type | Default | | | --- | --- | --- | --- | | `children` | `ReactNode` | | Required. The control. | | `label` | `ReactNode` | | A [`SectionLabel`](/ui/display/section-label) above the control. | | `hint` | `ReactNode` | | The line under the control that says what goes in it. | | `error` | `ReactNode` | | Shown under the control and marks it invalid. `null` or `''` shows nothing. | | `group` | `boolean` | `false` | Labels a group rather than one control. | | `orientation` | `'vertical' \| 'horizontal'` | `'vertical'` | `horizontal` puts the label beside the control. | | `className` | `string` | | | | `ref` | `Ref` | | | ## FieldHint and FormError `FieldHint` is a `

` in the hint's style with a 4 pixel margin above it. `FormError` is a `

` with `role="alert"`, so a screen reader announces it when it appears; use it for what went wrong in a dialog or a form as a whole. Both take `className`, `ref` and `render`. `FieldProps`, `FieldHintProps` and `FormErrorProps` are exported types. --- --- url: https://adecore.dev/ui/inputs/input.md --- # Input and TextArea The one text input: one border and one focus outline, at the normal height of 32 pixels or the compact 28. `TextArea` is a field of a few lines, such as a commit message or a note. ```tsx import { Input, TextArea } from '@adecore/ui'; ``` `size="sm"` is the height of a small button, for a row of tools or a name typed in place of the label it renames. `mono` switches to the code face for a branch, a path or anything read back character by character. `icon` draws a Lucide icon in front of the text, such as a magnifier on a search field. The icon is 14 pixels, or 12 with `size="sm"`. The input then sits in a box that draws the field, and `className` goes to that box so it lays out like any other field; `ref` and every other prop stay on the input. A value a person reads but does not type, such as a chosen folder beside the button that picks it, is an `Input` with `readOnly`. It shows the value in the muted text color and ends a long one with an ellipsis. It stays an input, so a screen reader announces it as read-only and the value can be copied. `end` draws something after the text inside the field, such as a count of matches and an [`IconButton`](/ui/actions/icon-button) of size `xs` that clears the query. Like `icon`, it puts the input in a box that draws the field, and `className` goes to that box. Keep `end` set for as long as the field is on screen, `false` while it has nothing to show, so the input is not mounted again and keeps its focus when the content appears. A `TextArea` is at least 64 pixels tall; `rows` makes it taller. `size="sm"` gives it the smaller text and padding of a compact input, for a commit message in a narrow panel. It has no resize handle until `resize="vertical"` gives it one. `variant="bare"` drops the border, the ground and the focus outline, for a text area inside a card that draws them: a message box with its buttons under the text. The card then shows the focus itself, for example with `has-[textarea:focus-visible]:outline-2 has-[textarea:focus-visible]:-outline-offset-1 has-[textarea:focus-visible]:outline-accent`. The border stays as a transparent line, so the text sits where it would in a field of its own. Inside a [`Field`](/ui/inputs/field) both take their `id`, `aria-describedby` and `aria-invalid` from it. Outside one, give them an `aria-label`. ## Props `Input` takes every prop of an `` except its numeric `size`, plus: | Prop | Type | Default | | | --- | --- | --- | --- | | `size` | `'md' \| 'sm'` | `'md'` | | | `mono` | `boolean` | `false` | | | `icon` | `LucideIcon` | | Drawn in front of the text. | | `end` | `ReactNode` | | Drawn after the text, inside the field. | `TextArea` takes every prop of a `