npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

classy-ink

v1.1.0

Published

Build beautiful CLI interfaces with utility classes and Ink

Downloads

3

Readme

Build classy CLI interfaces with Tailwind CSS-inspired utility classes and Ink.

Classy Ink is a simple drop-in replacement for the Box and Text Ink components. It adds support for utility classes through the class prop.


Try the demo now!

npx classy-ink

Or try it in your browser.

Install

npm install classy-ink

Example

import { render } from "ink";
import { Box, Text } from "classy-ink";

function Divider() {
  return <Box class="border-gray my-1 border-t" />;
}

function Button({ label }: { label: string }) {
  return (
    <Box class="border-round bg-blue px-1">
      <Text class="font-bold text-white">{label}</Text>
    </Box>
  );
}

function InputField({ label }: { label: string }) {
  return (
    <Box class="items-center">
      <Text class="mr-2">{label}:</Text>
      <Box class="border-round border-cyan border px-2">
        <Text class="text-cyan">_____________</Text>
      </Box>
    </Box>
  );
}

function App() {
  return (
    <Box class="border-round border-green flex-col border px-2 py-1">
      <Box class="justify-between">
        <Text class="text-magenta font-bold">The Matrix CLI</Text>
        <Text class="text-gray">(Ctrl+C to quit)</Text>
      </Box>
      <Divider />
      <Box class="flex-col gap-1">
        <Text class="text-red font-bold">Access credentials</Text>
        <Box class="gap-4">
          <InputField label="Username" />
          <InputField label="Password" />
        </Box>
      </Box>
      <Divider />
      <Box class="gap-1">
        <Button label="Enter" />
        <Button label="Cancel" />
      </Box>
    </Box>
  );
}

render(<App />);

You can run and edit this example live in your browser.

Features

  • Full support* for all of Box and Text style props.
  • Optimized for familiarity. Tailwind CSS users will feel right at home.
  • Compatible with Tailwind CSS Intellisense and automatic sorting.
  • Customizable screen variants (sm, md, lg...) to adapt to different terminal sizes. (coming soon)
  • Runtime compilation, which enables dynamic values like border-${color}.
  • Optional cache that prevents recompilation.

* While all props are supported, some small subsets of functionality are not fully implemented yet. See the Current limitations section for more information.

Contents

For a history of changes, see the changelog.

Usage

  1. Import Box and Text from classy-ink instead of ink.

    import { Box, Text } from "classy-ink";
  2. Use the class prop to apply styles.

    <Box class="min-w-1/2 flex-wrap gap-2 border px-3 py-1">
      <Text class="text-red">Hello</Text>
      <Text class="bg-blue font-bold text-white">World!</Text>
    </Box>

IDE features (optional)

While 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.

  1. Install the Tailwind CSS Intellisense extension for Visual Studio Code or any supported IDE.

  2. Install tailwindcss in your project.

    npm install -D tailwindcss
  3. Create a tailwind.config.js file in the project root with the following content:

    import { tailwindConfig } from "classy-ink/intellisense";
    
    export default tailwindConfig;

For automatic class sorting, set up the Tailwind CSS Prettier plugin.

Note that the Tailwind CSS configuration is NOT used for the actual compilation or anything else. It's only used for Intellisense.

Cache (optional)

To use the cache, wrap your app in a <ClassyInkProvider />, for example:

import { ClassyInkProvider, Box } from "classy-ink";

function App() {
  return (
    <ClassyInkProvider>
      <Box class="border p-1" />
    </ClassyInkProvider>
  );
}

The cache size can be configured with the maxCacheSize prop (default: 500). It can also be disabled by passing the value 0.

Note 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.

Also, note that the cache uses a Least Recently Used algorithm, in case that's relevant to your use case.

Tips

The 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.

For example, the following will work:

<Box class={`border-${color}-bright`} />

You can still pass any style props you want, and they will take precedence over the Classy Ink classes.

For example, in the following code the final value of flexDirection will be "row" instead of "column":

<Box class="flex-col" flexDirection="row" />

CLI 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.

The 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.


Utilities that support a numeric value (gap, m, grow...) also support the arbitrary value syntax (e.g. gap-[4]).


Classy 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.

Values 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.

Utility classes

All <Box /> and <Text /> style props are supported. Below are their equivalent Classy Ink utilities.

Box props

  • position: absolute and relative
  • columnGap: gap-x-<n>
  • rowGap: gap-y-<n>
  • gap: gap-<n>
  • margin: m-<n>
  • margin<X|Y|Top|Bottom|Left|Right>: m-<x|y|t|b|l|r>-<n>
  • padding: p-<n>
  • padding<X|Y|Top|Bottom|Left|Right>: p-<x|y|t|b|l|r>-<n>
  • flexGrow: grow (value: 1) and grow-<n>
  • flexShrink: shrink (value: 1) and shrink-<n>
  • flexDirection: flex-<row|row-reverse|column|column-reverse>
  • flexBasis: basis-<n>
  • flexWrap: flex-<nowrap|wrap|wrap-reverse>
  • alignItems: items-<start|center|end|stretch>
  • alignSelf: self-<start|center|end|auto>
  • justifyContent: justify-<start|end|between|around|center>
  • width: w-<n>, w-<n/n>, w-[<n>%] and w-full
  • height: h-<n>, h-<n/n>, h-[<n>%] and h-full
  • minWidth: min-w-<n>, min-w-<n/n>, min-w-[<n>%] and min-w-full
  • minHeight: min-h-<n>, min-h-<n/n>, min-h-[<n>%] and min-h-full
  • display: flex and hidden
  • borderStyle: border-<style>
  • border<Top|Bottom|Left|Right>Style: border-<t|b|l|r>
  • borderColor: border-<color>
  • border<Top|Bottom|Left|Right>Color: border-<t|b|l|r>-<color>
  • borderDimColor: border-dim
  • border<Top|Bottom|Left|Right>DimColor: border-<t|b|l|r>-dim
  • overflow: overflow-<visible|hidden>
  • overflow<X|Y>: overflow-<x|y>-<visible|hidden>

Text props

  • color: text-<color>
  • backgroundColor: bg-<color>
  • dimColor: text-dim
  • bold: font-bold
  • italic: italic
  • underline: underline
  • strikethrough: strike
  • inverse: inverse
  • wrap: whitespace-wrap, whitespace-nowrap (equivalent to truncate), truncate (truncates the end), truncate-<start|middle>

Notes

Colors

The following colors are supported:

  • black
  • white
  • gray
  • red
  • green
  • yellow
  • blue
  • cyan
  • magenta

All colors except gray also have a "bright" equivalent named <color>-bright (e.g. red-bright).

Borders

  • border sets borderStyle: "single" and enables all sides (borderTop, borderBottom, borderLeft, borderRight).
  • When border-<t|b|l|r> is set:
    • borderStyle is set to "single" unless another style is already set for all sides (border-<style>).
    • 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.

Current limitations

  • Setting border style by side/corner (border-<tl|t|tr|r|br|b|bl|l|a>-<style>) is not supported.
  • basis only supports basic numeric values.
  • 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.
  • There is no sense of "RTL" or "LTR" in Ink, so logical utilities like ms-<n> (margin-inline-start) are not supported.

Custom usage

If you have some kind of custom use case, you can use the useClassyInk hook or the compileClass function directly.

Both 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.

Unless there's a good reason to do otherwise, the hook is recommended over the function.

// note: incomplete example for illustration purposes
import { compileClass } from "classy-ink";
import { Box } from "ink";

function MyCustomBox({ class: className, ...props }) {
  return <Box {...useClassyInk(className)} {...props} />;
}

// or

const inkProps = compileClass("border border-red");
<Box {...inkProps} />;

Contributing

Install bun and install dependencies with bun i.

You can run bun demo:watch to start the demo and automatically restart on changes.

Contributions are welcome, especially those that add missing features like the ones listed in Current limitations.

Author

Classy Ink was built by Dani Guardiola.

Classy Ink is NOT affiliated with Tailwind CSS, Tailwind Labs Inc., or the Ink project.