Collection of customizable UI components for CLIs made with Ink
npm install @inkjs/ui
> Collection of customizable UI components for CLIs made with Ink.
``sh`
npm install @inkjs/ui
_This assumes you've already set up Ink. The easiest way to get started is create-ink-app._
TextInput is used for entering any single-line input with an optional autocomplete.
`jsx
import {TextInput} from '@inkjs/ui';
onSubmit={name => {
// name contains user input`
}}
/>;

EmailInput is used for entering an email. After "@" character is entered, domain can be autocompleted from the list of most popular email providers.
`jsx
import {EmailInput} from '@inkjs/ui';
onSubmit={email => {
// email contains user input`
}}
/>;

PasswordInput is used for entering sensitive data, like passwords, API keys and so on. It works the same way as TextInput, except input value is masked and replaced with asterisks ("\*").
`jsx
import {PasswordInput} from '@inkjs/ui';
onSubmit={password => {
// password contains user input`
}}
/>;

ConfirmInput shows a common "Y/n" input to confirm or cancel an operation your CLI wants to perform.
`jsx
import {ConfirmInput} from '@inkjs/ui';
// confirmed
}}
onCancel={() => {
// cancelled
}}
/>;
`

Select shows a scrollable list of options for a user to choose from.
`jsx
import {Select} from '@inkjs/ui';
options={[
{
label: 'Red',
value: 'red',
},
{
label: 'Green',
value: 'green',
},
{
label: 'Yellow',
value: 'yellow',
},
/ ... /
]}
onChange={newValue => {
// newValue equals the value field of the selected option`
// For example, "yellow"
}}
/>;

MultiSelect is similar to Select, except user can choose multiple options.
`jsx
import {MultiSelect} from '@inkjs/ui';
{
label: 'Red',
value: 'red',
},
{
label: 'Green',
value: 'green',
},
{
label: 'Yellow',
value: 'yellow',
},
/ ... /
]}
onChange={newValue => {
// newValue is an array of value fields of the selected options`
// For example, ["green", "yellow"]
}}
/>;

Spinner indicates that something is being processed and CLI is waiting for it to complete.
`jsx
import {Spinner} from '@inkjs/ui';
`

ProgressBar is an extended version of Spinner, where it's possible to calculate a progress percentage.
`jsx
import {ProgressBar} from '@inkjs/ui';
// progress must be a number between 0 and 100`

Badge can be used to indicate a status of a certain item, usually positioned nearby the element it's related to.
`jsx
import {Badge} from '@inkjs/ui';
`

StatusMessage can also be used to indicate a status, but when longer explanation of such status is required.
`jsx
import {StatusMessage} from '@inkjs/ui';
New version is deployed to production
Failed to deploy a new version of this app
Health checks aren't configured
This version is already deployed
`

Alert is used to focus user's attention to important messages.
`jsx
import {Alert} from '@inkjs/ui';
A new version of this CLI is available
Your license is expired
Current version of this CLI has been deprecated
API won't be available tomorrow night
`

UnorderedList is used to show lists of items.
`jsx
import {UnorderedList} from '@inkjs/ui';
;
`

OrderedList is used to show lists of numbered items.
`jsx
import {OrderedList} from '@inkjs/ui';
;
`

All component have their styles defined in a theme, which is accessible to components via React context. Ink UI ships with a default theme and it can be customized or replaced with a different theme altogether.
Let's get a quick look on how to customize a Spinner's component theme. Here's how it looks by default:

First, look up component's default theme, which will give an overview which parts does this component consist of. Documentation of each component includes a link to component's theme.ts file on top. In the case of Spinner, it's source/components/spinner/theme.ts.
Here's the part we care about:
`tsx
const theme = {
styles: {
container: (): BoxProps => ({
gap: 1,
}),
frame: (): TextProps => ({
color: 'blue',
}),
label: (): TextProps => ({}),
},
} satisfies ComponentTheme;
export default theme;
`
This component theme hints that Spinner has 3 parts: container, frame and a label. So to customize the color of the spinner itself, we'd want to change the color prop returned from the frame function.
To customize the default theme, use extendTheme function and make that custom theme available to children components via ThemeProvider.
`tsx
import {render, type TextProps} from 'ink';
import {Spinner, ThemeProvider, extendTheme, defaultTheme} from '@inkjs/ui';
const customTheme = extendTheme(defaultTheme, {
components: {
Spinner: {
styles: {
frame: (): TextProps => ({
color: 'magenta',
}),
},
},
},
});
function Example() {
return (
);
}
render(
`
With custom theme applied, Spinner now renders a magenta spinner, instead of the default blue one.

There are also cases where styles change based on some condition. For example, StatusMessage changes the color of an icon based on the variant prop.
Here's a sample code from its theme.
`ts
const colorByVariant = {
success: 'green',
error: 'red',
warning: 'yellow',
info: 'blue',
};
const theme = {
styles: {
icon: ({variant}) => ({
color: colorByVariant[variant],
}),
},
};
`
Since each field in styles object is a function, it can return different styles based on the props that were passed in or a state of a component.
Component themes can also include configuration for rendering a component in a config object, that's not related to styling. For example, UnorderedList specifies a marker, which is a character that's rendered before each list item.
Here's a sample code from its theme.
`ts`
const theme = {
config: () => ({
marker: '─',
}),
};

Changing marker to '+' would render this:

Components shipped in Ink UI automatically read the necessary styles and configuration from a theme. However, if you're adding a new custom component and a theme for it, use useComponentTheme hook to access it.
`tsx
import React, {render, Text, type TextProps} from 'ink';
import {
ThemeProvider,
defaultTheme,
extendTheme,
useComponentTheme,
type ComponentTheme,
} from '@inkjs/ui';
const customLabelTheme = {
styles: {
label: (): TextProps => ({
color: 'green',
}),
},
} satisfies ComponentTheme;
type CustomLabelTheme = typeof customLabelTheme;
const customTheme = extendTheme(defaultTheme, {
components: {
CustomLabel: customLabelTheme,
},
});
function CustomLabel() {
const {styles} = useComponentTheme
return
}
function Example() {
return (
);
}
render(
``