{"id":20797193,"url":"https://github.com/daniguardiola/classy-ink","last_synced_at":"2025-05-06T18:18:02.142Z","repository":{"id":205559027,"uuid":"713639491","full_name":"DaniGuardiola/classy-ink","owner":"DaniGuardiola","description":"Build classy CLI interfaces with utility classes and Ink.","archived":false,"fork":false,"pushed_at":"2023-11-16T19:35:27.000Z","size":239,"stargazers_count":53,"open_issues_count":8,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-05-06T18:17:56.894Z","etag":null,"topics":["cli","ink","javascript","tailwindcss","ui","utility-classes"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DaniGuardiola.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-11-02T23:52:20.000Z","updated_at":"2025-02-02T02:55:23.000Z","dependencies_parsed_at":"2024-11-17T16:36:42.441Z","dependency_job_id":"5801e9f5-70b8-4d3d-b069-3bb5b7d38da9","html_url":"https://github.com/DaniGuardiola/classy-ink","commit_stats":null,"previous_names":["daniguardiola/tailwind-ink","daniguardiola/classy-ink"],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DaniGuardiola%2Fclassy-ink","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DaniGuardiola%2Fclassy-ink/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DaniGuardiola%2Fclassy-ink/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DaniGuardiola%2Fclassy-ink/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DaniGuardiola","download_url":"https://codeload.github.com/DaniGuardiola/classy-ink/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252741483,"owners_count":21797030,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","ink","javascript","tailwindcss","ui","utility-classes"],"created_at":"2024-11-17T16:32:54.591Z","updated_at":"2025-05-06T18:18:02.120Z","avatar_url":"https://github.com/DaniGuardiola.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg alt=\"The Classy Ink logo\" src=\"https://github.com/DaniGuardiola/classy-ink/raw/main/logo.png\"\u003e\n\u003c/p\u003e\n\n\u003e Build classy CLI interfaces with [Tailwind CSS](https://tailwindcss.com)-inspired utility classes and [Ink](https://term.ink/).\n\nClassy Ink is a simple drop-in replacement for the `Box` and `Text` Ink components. It adds support for utility classes through the `class` prop.\n\n---\n\nTry the demo now!\n\n```\nnpx classy-ink\n```\n\nOr [try it in your browser](https://stackblitz.com/edit/classy-ink-demo?file=README\u0026view=editor).\n\n## \u003ca name='Install'\u003e\u003c/a\u003eInstall\n\n```\nnpm install classy-ink\n```\n\n## \u003ca name='Example'\u003e\u003c/a\u003eExample\n\n\u003cp align=\"center\"\u003e\n  \u003cimg alt=\"A screenshot of the example code below\" src=\"https://github.com/DaniGuardiola/classy-ink/raw/main/example.png\"\u003e\n\u003c/p\u003e\n\n```tsx\nimport { render } from \"ink\";\nimport { Box, Text } from \"classy-ink\";\n\nfunction Divider() {\n  return \u003cBox class=\"border-t border-gray my-1\" /\u003e;\n}\n\nfunction Button({ label }: { label: string }) {\n  return (\n    \u003cBox class=\"border-round bg-blue px-1\"\u003e\n      \u003cText class=\"text-white font-bold\"\u003e{label}\u003c/Text\u003e\n    \u003c/Box\u003e\n  );\n}\n\nfunction InputField({ label }: { label: string }) {\n  return (\n    \u003cBox class=\"items-center\"\u003e\n      \u003cText class=\"mr-2\"\u003e{label}:\u003c/Text\u003e\n      \u003cBox class=\"border border-round border-cyan px-2\"\u003e\n        \u003cText class=\"text-cyan\"\u003e_____________\u003c/Text\u003e\n      \u003c/Box\u003e\n    \u003c/Box\u003e\n  );\n}\n\nfunction App() {\n  return (\n    \u003cBox class=\"border border-round border-green flex-col px-2 py-1\"\u003e\n      \u003cBox class=\"justify-between\"\u003e\n        \u003cText class=\"text-magenta font-bold\"\u003eThe Matrix CLI\u003c/Text\u003e\n        \u003cText class=\"text-gray\"\u003e(Ctrl+C to quit)\u003c/Text\u003e\n      \u003c/Box\u003e\n      \u003cDivider /\u003e\n      \u003cBox class=\"flex-col gap-1\"\u003e\n        \u003cText class=\"text-red font-bold\"\u003eAccess credentials\u003c/Text\u003e\n        \u003cBox class=\"gap-4\"\u003e\n          \u003cInputField label=\"Username\" /\u003e\n          \u003cInputField label=\"Password\" /\u003e\n        \u003c/Box\u003e\n      \u003c/Box\u003e\n      \u003cDivider /\u003e\n      \u003cBox class=\"gap-1\"\u003e\n        \u003cButton label=\"Enter\" /\u003e\n        \u003cButton label=\"Cancel\" /\u003e\n      \u003c/Box\u003e\n    \u003c/Box\u003e\n  );\n}\n\nrender(\u003cApp /\u003e);\n```\n\nYou can [run and edit this example live](https://stackblitz.com/edit/classy-ink-example?file=example.tsx\u0026view=editor) in your browser.\n\n## \u003ca name='Features'\u003e\u003c/a\u003eFeatures\n\n- Full support\\* for all of `Box` and `Text` style props.\n- Optimized for familiarity. Tailwind CSS users will feel right at home.\n- Compatible with [Tailwind CSS Intellisense](https://tailwindcss.com/docs/editor-setup#intelli-sense-for-vs-code) and [automatic sorting](https://github.com/tailwindlabs/prettier-plugin-tailwindcss).\n- Customizable screen variants (`sm`, `md`, `lg`...) to adapt to different terminal sizes. _(coming soon)_\n- Runtime compilation, which enables dynamic values like `border-${color}`.\n- Optional cache that prevents recompilation.\n\n_\\* While all props are supported, some small subsets of functionality are not fully implemented yet. See the [Current limitations](#current-limitations) section for more information._\n\n## \u003ca name='Contents'\u003e\u003c/a\u003eContents\n\n\u003c!-- vscode-markdown-toc --\u003e\n\n- [Install](#install)\n- [Example](#example)\n- [Features](#features)\n- [Contents](#contents)\n- [Usage](#usage)\n  - [IDE features (optional)](#ide-features-optional)\n  - [Cache (optional)](#cache-optional)\n  - [Tips](#tips)\n- [Utility classes](#utility-classes)\n  - [`Box` props](#box-props)\n  - [`Text` props](#text-props)\n- [Notes](#notes)\n  - [Colors](#colors)\n  - [Borders](#borders)\n  - [Current limitations](#current-limitations)\n- [Custom usage](#custom-usage)\n- [Contributing](#contributing)\n- [Author](#author)\n\n\u003c!-- vscode-markdown-toc-config\n\tnumbering=false\n\tautoSave=true\n\t/vscode-markdown-toc-config --\u003e\n\u003c!-- /vscode-markdown-toc --\u003e\n\nFor a history of changes, see the [changelog](CHANGELOG.md).\n\n## \u003ca name='Usage'\u003e\u003c/a\u003eUsage\n\n1. Import `Box` and `Text` from `classy-ink` instead of `ink`.\n\n   ```tsx\n   import { Box, Text } from \"classy-ink\";\n   ```\n\n2. Use the `class` prop to apply styles.\n\n   ```tsx\n   \u003cBox class=\"min-w-1/2 border flex-wrap px-3 py-1 gap-2\"\u003e\n     \u003cText class=\"text-red\"\u003eHello\u003c/Text\u003e\n     \u003cText class=\"text-white bg-blue font-bold\"\u003eWorld!\u003c/Text\u003e\n   \u003c/Box\u003e\n   ```\n\n### \u003ca name='IDEfeaturesoptional'\u003e\u003c/a\u003eIDE features (optional)\n\nWhile Classy Ink is completely separate from Tailwind CSS, some tooling is compatible due to the similarities between the two projects. In particular, a big amount of effort was spent on Intellisense compatibility through a hand-made Tailwind CSS configuration.\n\n1. Install the [Tailwind CSS Intellisense extension](https://tailwindcss.com/docs/editor-setup#intelli-sense-for-vs-code) for Visual Studio Code or any supported IDE.\n\n2. Install `tailwindcss` in your project.\n\n   ```\n   npm install -D tailwindcss\n   ```\n\n3. Create a `tailwind.config.js` file in the project root with the following content:\n\n   ```tsx\n   import { tailwindConfig } from \"classy-ink/intellisense\";\n\n   export default tailwindConfig;\n   ```\n\nFor automatic class sorting, set up the [Tailwind CSS Prettier plugin](https://github.com/tailwindlabs/prettier-plugin-tailwindcss).\n\n\u003e Note that the Tailwind CSS configuration is NOT used for the actual compilation or anything else. It's only used for Intellisense.\n\n### \u003ca name='Cacheoptional'\u003e\u003c/a\u003eCache (optional)\n\nTo use the cache, wrap your app in a `\u003cClassyInkProvider /\u003e`, for example:\n\n```tsx\nimport { ClassyInkProvider, Box } from \"classy-ink\";\n\nfunction App() {\n  return (\n    \u003cClassyInkProvider\u003e\n      \u003cBox class=\"border p-1\" /\u003e\n    \u003c/ClassyInkProvider\u003e\n  );\n}\n```\n\nThe cache size can be configured with the `maxCacheSize` prop (default: `500`). It can also be disabled by passing the value `0`.\n\nNote that you might not need the cache at all. CLI apps are usually not very dynamic, so the performance impact of recompiling classes is negligible.\n\nAlso, note that the cache uses a [Least Recently Used](\u003chttps://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)\u003e) algorithm, in case that's relevant to your use case.\n\n### \u003ca name='Tips'\u003e\u003c/a\u003eTips\n\nThe compilation process occurs at runtime, so you can use dynamic values in your classes. If you're used to Tailwind CSS (where this is not possible), this might be a welcome difference.\n\nFor example, the following will work:\n\n```tsx\n\u003cBox class={`border-${color}-bright`} /\u003e\n```\n\n---\n\nYou can still pass any style props you want, and they will take precedence over the Classy Ink classes.\n\nFor example, in the following code the final value of `flexDirection` will be `\"row\"` instead of `\"column\"`:\n\n```tsx\n\u003cBox class=\"flex-col\" flexDirection=\"row\" /\u003e\n```\n\n---\n\nCLI apps are usually not very dynamic, so the cost of compiling (and recompiling) is often negligible. Furthermore, with the optional cache, it's even less of a problem. This means that normally there's no reason to use style props directly (over utility classes) for performance reasons.\n\nThe main exception is a highly dynamic value that changes very frequently. In that case, it's recommended to extract that value into a style prop while leaving others as utility classes.\n\n---\n\nUtilities that support a numeric value (`gap`, `m`, `grow`...) also support the arbitrary value syntax (e.g. `gap-[4]`).\n\n---\n\nClassy Ink is relatively lax about allowed values in comparison to Tailwind CSS. For example, `w-23/58` (equivalent to `width: 0.396551724%`) and `w-71827` will work out of the box, even though they are atypical.\n\nValues like these are not \"officially supported\" though, and might stop working in a future update. If you need them, use the arbitrary value syntax (e.g. `w-[0.396551%]` or `w-[71827]`) which will always support custom values.\n\n## \u003ca name='Utilityclasses'\u003e\u003c/a\u003eUtility classes\n\nAll `\u003cBox /\u003e` and `\u003cText /\u003e` style props are supported. Below are their equivalent Classy Ink utilities.\n\n### \u003ca name='Boxprops'\u003e\u003c/a\u003e`Box` props\n\n- `position`: `absolute` and `relative`\n- `columnGap`: `gap-x-\u003cn\u003e`\n- `rowGap`: `gap-y-\u003cn\u003e`\n- `gap`: `gap-\u003cn\u003e`\n- `margin`: `m-\u003cn\u003e`\n- `margin\u003cX|Y|Top|Bottom|Left|Right\u003e`: `m-\u003cx|y|t|b|l|r\u003e-\u003cn\u003e`\n- `padding`: `p-\u003cn\u003e`\n- `padding\u003cX|Y|Top|Bottom|Left|Right\u003e`: `p-\u003cx|y|t|b|l|r\u003e-\u003cn\u003e`\n- `flexGrow`: `grow` (value: `1`) and `grow-\u003cn\u003e`\n- `flexShrink`: `shrink` (value: `1`) and `shrink-\u003cn\u003e`\n- `flexDirection`: `flex-\u003crow|row-reverse|column|column-reverse\u003e`\n- `flexBasis`: `basis-\u003cn\u003e`\n- `flexWrap`: `flex-\u003cnowrap|wrap|wrap-reverse\u003e`\n- `alignItems`: `items-\u003cstart|center|end|stretch\u003e`\n- `alignSelf`: `self-\u003cstart|center|end|auto\u003e`\n- `justifyContent`: `justify-\u003cstart|end|between|around|center\u003e`\n- `width`: `w-\u003cn\u003e`, `w-\u003cn/n\u003e`, `w-[\u003cn\u003e%]` and `w-full`\n- `height`: `h-\u003cn\u003e`, `h-\u003cn/n\u003e`, `h-[\u003cn\u003e%]` and `h-full`\n- `minWidth`: `min-w-\u003cn\u003e`, `min-w-\u003cn/n\u003e`, `min-w-[\u003cn\u003e%]` and `min-w-full`\n- `minHeight`: `min-h-\u003cn\u003e`, `min-h-\u003cn/n\u003e`, `min-h-[\u003cn\u003e%]` and `min-h-full`\n- `display`: `flex` and `hidden`\n- `borderStyle`: `border-\u003cstyle\u003e`\n- `border\u003cTop|Bottom|Left|Right\u003eStyle`: `border-\u003ct|b|l|r\u003e`\n- `borderColor`: `border-\u003ccolor\u003e`\n- `border\u003cTop|Bottom|Left|Right\u003eColor`: `border-\u003ct|b|l|r\u003e-\u003ccolor\u003e`\n- `borderDimColor`: `border-dim`\n- `border\u003cTop|Bottom|Left|Right\u003eDimColor`: `border-\u003ct|b|l|r\u003e-dim`\n- `overflow`: `overflow-\u003cvisible|hidden\u003e`\n- `overflow\u003cX|Y\u003e`: `overflow-\u003cx|y\u003e-\u003cvisible|hidden\u003e`\n\n### \u003ca name='Textprops'\u003e\u003c/a\u003e`Text` props\n\n- `color`: `text-\u003ccolor\u003e`\n- `backgroundColor`: `bg-\u003ccolor\u003e`\n- `dimColor`: `text-dim`\n- `bold`: `font-bold`\n- `italic`: `italic`\n- `underline`: `underline`\n- `strikethrough`: `strike`\n- `inverse`: `inverse`\n- `wrap`: `whitespace-wrap`, `whitespace-nowrap` (equivalent to `truncate`), `truncate` (truncates the end), `truncate-\u003cstart|middle\u003e`\n\n## \u003ca name='Notes'\u003e\u003c/a\u003eNotes\n\n### \u003ca name='Colors'\u003e\u003c/a\u003eColors\n\nThe following colors are supported:\n\n- `black`\n- `white`\n- `gray`\n- `red`\n- `green`\n- `yellow`\n- `blue`\n- `cyan`\n- `magenta`\n\nAll colors except `gray` also have a \"bright\" equivalent named `\u003ccolor\u003e-bright` (e.g. `red-bright`).\n\n### \u003ca name='Borders'\u003e\u003c/a\u003eBorders\n\n- `border` sets `borderStyle: \"single\"` and enables all sides (`borderTop`, `borderBottom`, `borderLeft`, `borderRight`).\n- When `border-\u003ct|b|l|r\u003e` is set:\n  - `borderStyle` is set to `\"single\"` unless another style is already set **for all sides** (`border-\u003cstyle\u003e`).\n  - All other sides are disabled (set to `false`) unless enabled elsewhere. In other words, it functions as a \"whitelist\". Note that `border` always enables all sides.\n\n### \u003ca name='Currentlimitations'\u003e\u003c/a\u003eCurrent limitations\n\n- Setting border style by side/corner (`border-\u003ctl|t|tr|r|br|b|bl|l|a\u003e-\u003cstyle\u003e`) is not supported.\n- `basis` only supports basic numeric values.\n- Margin utilities only support negative values through arbitrary value syntax (e.g. `ml-[-1]`). Standard syntax (e.g. `-ml-1`) is not supported. Negative percentages are not supported either.\n- There is no sense of \"RTL\" or \"LTR\" in Ink, so logical utilities like `ms-\u003cn\u003e` (`margin-inline-start`) are not supported.\n\n## Custom usage\n\nIf you have some kind of custom use case, you can use the `useClassyInk` hook or the `compileClass` function directly.\n\nBoth take a class string and return an object with the corresponding Ink props. The hook wraps the function and adds memoization and caching logic on top.\n\nUnless there's a good reason to do otherwise, the hook is recommended over the function.\n\n```tsx\n// note: incomplete example for illustration purposes\nimport { compileClass } from \"classy-ink\";\nimport { Box } from \"ink\";\n\nfunction MyCustomBox({ class: className, ...props }) {\n  return \u003cBox {...useClassyInk(className)} {...props} /\u003e;\n}\n\n// or\n\nconst inkProps = compileClass(\"border border-red\");\n\u003cBox {...inkProps} /\u003e;\n```\n\n## \u003ca name='Contributing'\u003e\u003c/a\u003eContributing\n\nInstall [`bun`](https://bun.sh/) and install dependencies with `bun i`.\n\nYou can run `bun demo:watch` to start the demo and automatically restart on changes.\n\nContributions are welcome, especially those that add missing features like the ones listed in [Current limitations](#current-limitations).\n\n## \u003ca name='Author'\u003e\u003c/a\u003eAuthor\n\nClassy Ink was built by [Dani Guardiola](https://dio.la/).\n\nClassy Ink is NOT affiliated with Tailwind CSS, Tailwind Labs Inc., or the Ink project.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdaniguardiola%2Fclassy-ink","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdaniguardiola%2Fclassy-ink","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdaniguardiola%2Fclassy-ink/lists"}