{"_id":"@inkjs/ui","_rev":"4107161","name":"@inkjs/ui","description":"Collection of customizable UI components for CLIs made with Ink","dist-tags":{"latest":"2.0.0"},"maintainers":[{"name":"sindresorhus","email":""},{"name":"vdemedes","email":"sbioko@gmail.com"}],"time":{"modified":"2026-01-23T00:44:17.000Z","created":"2023-05-07T10:23:25.977Z","2.0.0":"2024-05-22T12:20:05.842Z","1.0.0":"2023-05-07T10:30:20.174Z","0.0.1":"2023-05-07T10:23:25.977Z"},"users":{},"author":{"name":"Vadim Demedes","email":"vadimdemedes@hey.com","url":"https://github.com/vadimdemedes"},"repository":{"type":"git","url":"git+https://github.com/vadimdemedes/ink-ui.git"},"versions":{"2.0.0":{"name":"@inkjs/ui","version":"2.0.0","description":"Collection of customizable UI components for CLIs made with Ink","license":"MIT","repository":{"type":"git","url":"git+https://github.com/vadimdemedes/ink-ui.git"},"author":{"name":"Vadim Demedes","email":"vadimdemedes@hey.com","url":"https://github.com/vadimdemedes"},"publishConfig":{"access":"public"},"type":"module","exports":{"types":"./build/index.d.ts","default":"./build/index.js"},"engines":{"node":">=18"},"scripts":{"dev":"tsc --watch","build":"tsc","prepare":"npm run build","test":"tsc --noEmit && xo && ava","example":"NODE_NO_WARNINGS=1 node --import=tsimp"},"dependencies":{"chalk":"^5.3.0","cli-spinners":"^3.0.0","deepmerge":"^4.3.1","figures":"^6.1.0"},"devDependencies":{"@sindresorhus/tsconfig":"^5.0.0","@types/react":"^18.3.2","@vdemedes/prettier-config":"^2.0.1","ava":"^5.2.0","boxen":"^7.1.1","cat-names":"^4.0.0","delay":"^6.0.0","eslint-config-xo-react":"^0.27.0","eslint-plugin-react":"^7.34.1","eslint-plugin-react-hooks":"^4.6.2","ink":"^5.0.0","ink-testing-library":"^4.0.0","prettier":"^3.2.5","react":"^18.3.1","tsimp":"^2.0.11","typescript":"^5.4.5","xo":"^0.58.0"},"peerDependencies":{"ink":">=5"},"ava":{"extensions":{"ts":"module","tsx":"module"},"nodeArguments":["--import=tsimp"],"environmentVariables":{"NODE_NO_WARNINGS":"1","FORCE_COLOR":"true"}},"xo":{"extends":["xo-react"],"plugins":["react"],"prettier":true,"rules":{"react/no-unescaped-entities":"off","unicorn/prevent-abbreviations":"off"}},"prettier":"@vdemedes/prettier-config","_id":"@inkjs/ui@2.0.0","gitHead":"14b1145da0123a48cfc2f0ec9ff33dff0633f464","bugs":{"url":"https://github.com/vadimdemedes/ink-ui/issues"},"homepage":"https://github.com/vadimdemedes/ink-ui#readme","_nodeVersion":"18.20.2","_npmVersion":"10.6.0","dist":{"shasum":"e82e49cd3e4feef6d3b0bddd38525e0d734d221a","size":27464,"noattachment":false,"key":"/@inkjs/ui/-/@inkjs/ui-2.0.0.tgz","tarball":"http://registry.cnpm.dingdandao.com/@inkjs/ui/download/@inkjs/ui-2.0.0.tgz"},"_npmUser":{"name":"sindresorhus","email":"sindresorhus@gmail.com"},"directories":{},"maintainers":[{"name":"sindresorhus","email":""},{"name":"vdemedes","email":"sbioko@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/ui_2.0.0_1716380405625_0.9877224850179844"},"_hasShrinkwrap":false,"_cnpmcore_publish_time":"2024-05-22T12:20:05.842Z","publish_time":1716380405842,"_source_registry_name":"default","_cnpm_publish_time":1716380405842},"1.0.0":{"name":"@inkjs/ui","version":"1.0.0","description":"Collection of customizable UI components for CLIs made with Ink","license":"MIT","repository":{"type":"git","url":"git+https://github.com/vadimdemedes/ink-ui.git"},"author":{"name":"Vadim Demedes","email":"vadimdemedes@hey.com","url":"https://github.com/vadimdemedes"},"publishConfig":{"access":"public"},"type":"module","exports":{"types":"./build/index.d.ts","default":"./build/index.js"},"engines":{"node":">=14.16"},"scripts":{"dev":"tsc --watch","build":"tsc","prepare":"npm run build","test":"tsc --noEmit && xo && ava","example":"NODE_NO_WARNINGS=1 node --loader=ts-node/esm"},"dependencies":{"chalk":"^5.2.0","cli-spinners":"^2.9.0","deepmerge":"^4.3.1","figures":"^5.0.0"},"devDependencies":{"@sindresorhus/tsconfig":"^3.0.1","@types/react":"^18.2.0","@vdemedes/prettier-config":"^2.0.1","ava":"^5.2.0","boxen":"^7.0.2","cat-names":"^3.1.0","delay":"^5.0.0","eslint-config-xo-react":"^0.27.0","eslint-plugin-react":"^7.32.2","eslint-plugin-react-hooks":"^4.6.0","ink":"^4.2.0","ink-testing-library":"^3.0.0","prettier":"^2.8.8","react":"^18.2.0","ts-node":"^10.9.1","typescript":"^5.0.4","xo":"^0.54.1"},"peerDependencies":{"ink":"^4.2.0"},"ava":{"extensions":{"ts":"module","tsx":"module"},"nodeArguments":["--loader=ts-node/esm"],"environmentVariables":{"NODE_NO_WARNINGS":"1","FORCE_COLOR":"true"}},"xo":{"extends":["xo-react"],"plugins":["react"],"prettier":true,"rules":{"react/no-unescaped-entities":"off"}},"prettier":"@vdemedes/prettier-config","gitHead":"8fed1248c3ae7239d8ce8e925b44ea6f2443a2f1","bugs":{"url":"https://github.com/vadimdemedes/ink-ui/issues"},"homepage":"https://github.com/vadimdemedes/ink-ui#readme","_id":"@inkjs/ui@1.0.0","_nodeVersion":"16.19.1","_npmVersion":"8.19.3","dist":{"shasum":"e4fa6272ff5bb876c01a882be876e219d0356e78","size":48596,"noattachment":false,"key":"/@inkjs/ui/-/@inkjs/ui-1.0.0.tgz","tarball":"http://registry.cnpm.dingdandao.com/@inkjs/ui/download/@inkjs/ui-1.0.0.tgz"},"_npmUser":{"name":"vdemedes","email":"vadimdemedes@hey.com"},"directories":{},"maintainers":[{"name":"sindresorhus","email":""},{"name":"vdemedes","email":"sbioko@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/ui_1.0.0_1683455419971_0.0760566731848753"},"_hasShrinkwrap":false,"_cnpmcore_publish_time":"2023-05-07T10:30:20.174Z","publish_time":1683455420174,"_source_registry_name":"default","_cnpm_publish_time":1683455420174},"0.0.1":{"name":"@inkjs/ui","version":"0.0.1","description":"Collection of customizable UI components for CLIs made with Ink","license":"MIT","repository":{"type":"git","url":"git+https://github.com/vadimdemedes/ink-ui.git"},"author":{"name":"Vadim Demedes","email":"vadimdemedes@hey.com","url":"https://github.com/vadimdemedes"},"publishConfig":{"access":"public"},"type":"module","exports":{"types":"./build/index.d.ts","default":"./build/index.js"},"engines":{"node":">=14.16"},"scripts":{"dev":"tsc --watch","build":"tsc","prepare":"npm run build","test":"tsc --noEmit && xo && ava","example":"NODE_NO_WARNINGS=1 node --loader=ts-node/esm"},"dependencies":{"chalk":"^5.2.0","cli-spinners":"^2.9.0","deepmerge":"^4.3.1","figures":"^5.0.0"},"devDependencies":{"@sindresorhus/tsconfig":"^3.0.1","@types/react":"^18.2.0","@vdemedes/prettier-config":"^2.0.1","ava":"^5.2.0","boxen":"^7.0.2","cat-names":"^3.1.0","delay":"^5.0.0","eslint-config-xo-react":"^0.27.0","eslint-plugin-react":"^7.32.2","eslint-plugin-react-hooks":"^4.6.0","ink":"^4.2.0","ink-testing-library":"^3.0.0","prettier":"^2.8.8","react":"^18.2.0","ts-node":"^10.9.1","typescript":"^5.0.4","xo":"^0.54.1"},"peerDependencies":{"ink":"^4.2.0"},"ava":{"extensions":{"ts":"module","tsx":"module"},"nodeArguments":["--loader=ts-node/esm"],"environmentVariables":{"NODE_NO_WARNINGS":"1","FORCE_COLOR":"true"}},"xo":{"extends":["xo-react"],"plugins":["react"],"prettier":true,"rules":{"react/no-unescaped-entities":"off"}},"prettier":"@vdemedes/prettier-config","gitHead":"c2cf7e35b28bb8495cfbf80d891f5437bd5238a2","bugs":{"url":"https://github.com/vadimdemedes/ink-ui/issues"},"homepage":"https://github.com/vadimdemedes/ink-ui#readme","_id":"@inkjs/ui@0.0.1","_nodeVersion":"18.16.0","_npmVersion":"9.5.1","dist":{"shasum":"27d92ba0e25628476541527a5289dc77baf67ca3","size":48596,"noattachment":false,"key":"/@inkjs/ui/-/@inkjs/ui-0.0.1.tgz","tarball":"http://registry.cnpm.dingdandao.com/@inkjs/ui/download/@inkjs/ui-0.0.1.tgz"},"_npmUser":{"name":"vdemedes","email":"vadimdemedes@hey.com"},"directories":{},"maintainers":[{"name":"sindresorhus","email":""},{"name":"vdemedes","email":"sbioko@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/ui_0.0.1_1683455005752_0.370355831896245"},"_hasShrinkwrap":false,"_cnpmcore_publish_time":"2023-05-07T10:23:25.977Z","publish_time":1683455005977,"_source_registry_name":"default","_cnpm_publish_time":1683455005977}},"readme":"[![](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md)\n\n# Ink UI [![test](https://github.com/vadimdemedes/ink-ui/actions/workflows/test.yml/badge.svg)](https://github.com/vadimdemedes/ink-ui/actions/workflows/test.yml)\n\n> Collection of customizable UI components for CLIs made with [Ink](https://term.ink).\n\n## Install\n\n```sh\nnpm install @inkjs/ui\n```\n\n_This assumes you've already set up [Ink](https://term.ink). The easiest way to get started is [create-ink-app](https://github.com/vadimdemedes/create-ink-app)._\n\n## Components\n\n### Text input\n\n[Documentation](docs/text-input.md)\n\n`TextInput` is used for entering any single-line input with an optional autocomplete.\n\n```jsx\nimport {TextInput} from '@inkjs/ui';\n\n<TextInput\n\tplaceholder=\"Enter your name...\"\n\tonSubmit={name => {\n\t\t// `name` contains user input\n\t}}\n/>;\n```\n\n<img src=\"media/text-input.gif\" width=\"400\">\n\n### Email input\n\n[Documentation](docs/email-input.md)\n\n`EmailInput` is used for entering an email. After \"@\" character is entered, domain can be autocompleted from the list of most popular email providers.\n\n```jsx\nimport {EmailInput} from '@inkjs/ui';\n\n<EmailInput\n\tplaceholder=\"Enter email...\"\n\tonSubmit={email => {\n\t\t// `email` contains user input\n\t}}\n/>;\n```\n\n<img src=\"media/email-input.gif\" width=\"400\">\n\n### Password input\n\n[Documentation](docs/password-input.md)\n\n`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 (\"\\*\").\n\n```jsx\nimport {PasswordInput} from '@inkjs/ui';\n\n<PasswordInput\n\tplaceholder=\"Enter password...\"\n\tonSubmit={password => {\n\t\t// `password` contains user input\n\t}}\n/>;\n```\n\n<img src=\"media/password-input.gif\" width=\"400\">\n\n### Confirm input\n\n[Documentation](docs/confirm-input.md)\n\n`ConfirmInput` shows a common \"Y/n\" input to confirm or cancel an operation your CLI wants to perform.\n\n```jsx\nimport {ConfirmInput} from '@inkjs/ui';\n\n<ConfirmInput\n\tonConfirm={() => {\n\t\t// confirmed\n\t}}\n\tonCancel={() => {\n\t\t// cancelled\n\t}}\n/>;\n```\n\n<img src=\"media/confirm-input.png\" width=\"200\">\n\n### Select\n\n[Documentation](docs/select.md)\n\n`Select` shows a scrollable list of options for a user to choose from.\n\n```jsx\nimport {Select} from '@inkjs/ui';\n\n<Select\n\toptions={[\n\t\t{\n\t\t\tlabel: 'Red',\n\t\t\tvalue: 'red',\n\t\t},\n\t\t{\n\t\t\tlabel: 'Green',\n\t\t\tvalue: 'green',\n\t\t},\n\t\t{\n\t\t\tlabel: 'Yellow',\n\t\t\tvalue: 'yellow',\n\t\t},\n\t\t/* ... */\n\t]}\n\tonChange={newValue => {\n\t\t// `newValue` equals the `value` field of the selected option\n\t\t// For example, \"yellow\"\n\t}}\n/>;\n```\n\n<img src=\"media/select.gif\" width=\"400\">\n\n### Multi select\n\n[Documentation](docs/multi-select.md)\n\n`MultiSelect` is similar to `Select`, except user can choose multiple options.\n\n```jsx\nimport {MultiSelect} from '@inkjs/ui';\n\n<MultiSelect\n\toptions={[\n\t\t{\n\t\t\tlabel: 'Red',\n\t\t\tvalue: 'red',\n\t\t},\n\t\t{\n\t\t\tlabel: 'Green',\n\t\t\tvalue: 'green',\n\t\t},\n\t\t{\n\t\t\tlabel: 'Yellow',\n\t\t\tvalue: 'yellow',\n\t\t},\n\t\t/* ... */\n\t]}\n\tonChange={newValue => {\n\t\t// `newValue` is an array of `value` fields of the selected options\n\t\t// For example, [\"green\", \"yellow\"]\n\t}}\n/>;\n```\n\n<img src=\"media/multi-select.gif\" width=\"400\">\n\n### Spinner\n\n[Documentation](docs/spinner.md)\n\n`Spinner` indicates that something is being processed and CLI is waiting for it to complete.\n\n```jsx\nimport {Spinner} from '@inkjs/ui';\n\n<Spinner label=\"Loading\" />;\n```\n\n<img src=\"media/spinner.gif\" width=\"400\">\n\n### Progress bar\n\n[Documentation](docs/progress-bar.md)\n\n`ProgressBar` is an extended version of `Spinner`, where it's possible to calculate a progress percentage.\n\n```jsx\nimport {ProgressBar} from '@inkjs/ui';\n\n// `progress` must be a number between 0 and 100\n<ProgressBar value={progress} />;\n```\n\n<img src=\"media/progress-bar.gif\" width=\"400\">\n\n### Badge\n\n[Documentation](docs/badge.md)\n\n`Badge` can be used to indicate a status of a certain item, usually positioned nearby the element it's related to.\n\n```jsx\nimport {Badge} from '@inkjs/ui';\n\n<Badge color=\"green\">Pass</Badge>\n<Badge color=\"red\">Fail</Badge>\n<Badge color=\"yellow\">Warn</Badge>\n<Badge color=\"blue\">Todo</Badge>\n```\n\n<img src=\"media/badge.png\" width=\"400\">\n\n### Status message\n\n[Documentation](docs/status-message.md)\n\n`StatusMessage` can also be used to indicate a status, but when longer explanation of such status is required.\n\n```jsx\nimport {StatusMessage} from '@inkjs/ui';\n\n<StatusMessage variant=\"success\">\n\tNew version is deployed to production\n</StatusMessage>\n\n<StatusMessage variant=\"error\">\n  Failed to deploy a new version of this app\n</StatusMessage>\n\n<StatusMessage variant=\"warning\">\n    Health checks aren't configured\n</StatusMessage>\n\n<StatusMessage variant=\"info\">\n    This version is already deployed\n</StatusMessage>\n```\n\n<img src=\"media/status-message.png\" width=\"400\">\n\n### Alert\n\n[Documentation](docs/alert.md)\n\n`Alert` is used to focus user's attention to important messages.\n\n```jsx\nimport {Alert} from '@inkjs/ui';\n\n<Alert variant=\"success\">\n    A new version of this CLI is available\n</Alert>\n\n<Alert variant=\"error\">\n    Your license is expired\n</Alert>\n\n<Alert variant=\"warning\">\n    Current version of this CLI has been deprecated\n</Alert>\n\n<Alert variant=\"info\">\n    API won't be available tomorrow night\n</Alert>\n```\n\n<img src=\"media/alert.png\" width=\"600\">\n\n### Unordered list\n\n[Documentation](docs/unordered-list.md)\n\n`UnorderedList` is used to show lists of items.\n\n```jsx\nimport {UnorderedList} from '@inkjs/ui';\n\n<UnorderedList>\n\t<UnorderedList.Item>\n\t\t<Text>Red</Text>\n\t</UnorderedList.Item>\n\n\t<UnorderedList.Item>\n\t\t<Text>Green</Text>\n\n\t\t<UnorderedList>\n\t\t\t<UnorderedList.Item>\n\t\t\t\t<Text>Light</Text>\n\t\t\t</UnorderedList.Item>\n\n\t\t\t<UnorderedList.Item>\n\t\t\t\t<Text>Dark</Text>\n\t\t\t</UnorderedList.Item>\n\t\t</UnorderedList>\n\t</UnorderedList.Item>\n\n\t<UnorderedList.Item>\n\t\t<Text>Blue</Text>\n\t</UnorderedList.Item>\n</UnorderedList>;\n```\n\n<img src=\"media/unordered-list.png\" width=\"400\">\n\n### Ordered list\n\n[Documentation](docs/ordered-list.md)\n\n`OrderedList` is used to show lists of numbered items.\n\n```jsx\nimport {OrderedList} from '@inkjs/ui';\n\n<OrderedList>\n\t<OrderedList.Item>\n\t\t<Text>Red</Text>\n\t</OrderedList.Item>\n\n\t<OrderedList.Item>\n\t\t<Text>Green</Text>\n\n\t\t<OrderedList>\n\t\t\t<OrderedList.Item>\n\t\t\t\t<Text>Light</Text>\n\t\t\t</OrderedList.Item>\n\n\t\t\t<OrderedList.Item>\n\t\t\t\t<Text>Dark</Text>\n\t\t\t</OrderedList.Item>\n\t\t</OrderedList>\n\t</OrderedList.Item>\n\n\t<OrderedList.Item>\n\t\t<Text>Blue</Text>\n\t</OrderedList.Item>\n</OrderedList>;\n```\n\n<img src=\"media/ordered-list.png\" width=\"400\">\n\n## Theming\n\nAll 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.\n\nLet's get a quick look on how to customize a `Spinner`'s component theme. Here's how it looks by default:\n\n<img src=\"media/spinner.gif\" width=\"400\">\n\nFirst, 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](source/components/spinner/theme.ts).\n\nHere's the part we care about:\n\n```tsx\nconst theme = {\n\tstyles: {\n\t\tcontainer: (): BoxProps => ({\n\t\t\tgap: 1,\n\t\t}),\n\t\tframe: (): TextProps => ({\n\t\t\tcolor: 'blue',\n\t\t}),\n\t\tlabel: (): TextProps => ({}),\n\t},\n} satisfies ComponentTheme;\n\nexport default theme;\n```\n\nThis 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.\n\nTo customize the default theme, use `extendTheme` function and make that custom theme available to children components via `ThemeProvider`.\n\n```tsx\nimport {render, type TextProps} from 'ink';\nimport {Spinner, ThemeProvider, extendTheme, defaultTheme} from '@inkjs/ui';\n\nconst customTheme = extendTheme(defaultTheme, {\n\tcomponents: {\n\t\tSpinner: {\n\t\t\tstyles: {\n\t\t\t\tframe: (): TextProps => ({\n\t\t\t\t\tcolor: 'magenta',\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t},\n});\n\nfunction Example() {\n\treturn (\n\t\t<ThemeProvider theme={customTheme}>\n\t\t\t<Spinner label=\"Loading\" />\n\t\t</ThemeProvider>\n\t);\n}\n\nrender(<Example />);\n```\n\nWith custom theme applied, `Spinner` now renders a magenta spinner, instead of the default blue one.\n\n<img src=\"media/spinner-theme.gif\" width=\"400\">\n\nThere are also cases where styles change based on some condition. For example, [`StatusMessage`](docs/status-message.md) changes the color of an icon based on the `variant` prop.\n\nHere's a sample code from its [theme](source/components/status-message/theme.ts).\n\n```ts\nconst colorByVariant = {\n\tsuccess: 'green',\n\terror: 'red',\n\twarning: 'yellow',\n\tinfo: 'blue',\n};\n\nconst theme = {\n\tstyles: {\n\t\ticon: ({variant}) => ({\n\t\t\tcolor: colorByVariant[variant],\n\t\t}),\n\t},\n};\n```\n\nSince 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.\n\nComponent themes can also include configuration for rendering a component in a `config` object, that's not related to styling. For example, [`UnorderedList`](docs/unordered-list.md) specifies a `marker`, which is a character that's rendered before each list item.\n\nHere's a sample code from its [theme](source/components/unordered-list/theme.ts).\n\n```ts\nconst theme = {\n\tconfig: () => ({\n\t\tmarker: '─',\n\t}),\n};\n```\n\n<img src=\"media/unordered-list.png\" width=\"400\">\n\nChanging `marker` to `'+'` would render this:\n\n<img src=\"media/unordered-list-theme.png\" width=\"400\">\n\nComponents 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.\n\n```tsx\nimport React, {render, Text, type TextProps} from 'ink';\nimport {\n\tThemeProvider,\n\tdefaultTheme,\n\textendTheme,\n\tuseComponentTheme,\n\ttype ComponentTheme,\n} from '@inkjs/ui';\n\nconst customLabelTheme = {\n\tstyles: {\n\t\tlabel: (): TextProps => ({\n\t\t\tcolor: 'green',\n\t\t}),\n\t},\n} satisfies ComponentTheme;\n\ntype CustomLabelTheme = typeof customLabelTheme;\n\nconst customTheme = extendTheme(defaultTheme, {\n\tcomponents: {\n\t\tCustomLabel: customLabelTheme,\n\t},\n});\n\nfunction CustomLabel() {\n\tconst {styles} = useComponentTheme<CustomLabelTheme>('CustomLabel');\n\n\treturn <Text {...styles.label()}>Hello world</Text>;\n}\n\nfunction Example() {\n\treturn (\n\t\t<ThemeProvider theme={customTheme}>\n\t\t\t<CustomLabel />\n\t\t</ThemeProvider>\n\t);\n}\n\nrender(<Example />);\n```\n","_attachments":{},"homepage":"https://github.com/vadimdemedes/ink-ui#readme","bugs":{"url":"https://github.com/vadimdemedes/ink-ui/issues"},"license":"MIT"}