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 🙏

© 2026 – Pkg Stats / Ryan Hefner

technoart

v0.1.0

Published

A constrained CSS utility library

Downloads

109

Readme

Technoart

A constrained CSS utility library. Not a framework, a discipline.

The constraint is the product. Spacing follows a 4pt grid. Breakpoints are fixed. Base components are structural. Colors, brand tokens, and visual opinions belong to your team.

Technoart ships two things: a compiled CSS file, and a philosophy. Use the file, or use just the idea.

Full build is ~125kb. With PurgeCSS in production, real-world usage drops this to a few kb.


Get Started

Use the file - drop in and go.

CDN:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/technoart/dist/technoart.css" />

npm:

npm install technoart
@import 'technoart/dist/technoart.css';

After dropping in the CSS, define your color tokens once in your own stylesheet. Technoart ships no colors. That's intentional. Here's a common starting point:

/* your stylesheet, loaded after technoart.css */
:root {
  --color-primary: hsl(182, 70%, 40%);
  --color-text: hsl(235, 12%, 20%);
  --color-bg: hsl(210, 30%, 98%);
  --color-surface: #fff;
  --color-border: hsl(210, 17%, 88%);
  --color-muted: hsl(208, 17%, 55%);
}
body {
  color: var(--color-text);
  background: var(--color-bg);
  font-family: system-ui, sans-serif;
}

These aren't Technoart's variables. They're yours. Change the names, change the values, add or remove. The library works with whatever you define. See demo/index.html for a working example of this pattern.


Use the idea - fork and own.

index.scss           ← entry point
_variable.scss       ← shared variables (spacing scale, breakpoints)
_mixin.scss          ← mixins
core/                ← individual utility files
demo/index.html      ← visual reference for every utility
git clone https://github.com/yourusername/technoart
npm install
npm run build        # compile to dist/technoart.css
npm run dev          # watch mode

Modify _variable.scss to adjust the spacing scale, breakpoints, or structural defaults. A team that forks Technoart now owns their system. That's the intended outcome.


Breakpoints

Three fixed breakpoints. Breakpoint suffix = that size and below (max-width).

| Suffix | Value | Applies when | |--------|--------|---------------------------| | -sm | 640px | ≤ 640px | | -md | 768px | ≤ 768px | | -lg | 1024px | ≤ 1024px |

<!-- 32px padding on desktop, 16px on mobile -->
<div class="padding-32 padding-16-sm">...</div>

<!-- Row on desktop, column on mobile -->
<div class="flex flex-row flex-column-sm">...</div>

One direction only. Base styles are your default (typically desktop). Breakpoint suffixes are overrides for smaller screens. The only exception is Hidden, which supports both directions by design.

Why desktop-first and not mobile-first?

Tailwind and most utility frameworks default to mobile-first, where md: means 768px and above. Technoart defaults to desktop-first, where -md means 768px and below.

Web apps are primarily desktop experiences. The base style is what most users see most of the time. Mobile is an override. Desktop-first reads naturally for app UIs: "this is the layout, on smaller screens it adapts." Mobile-first makes more sense for content sites where the reading experience starts on phone. For app development, write the default first, add breakpoint suffixes as exceptions.


Spacing

4pt grid. Responsive: yes

Values: 0, 2, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 80, 100, 120, 140

2 is a deliberate exception, outside the 4pt grid, useful for hairline gaps and micro-nudges.

| Class | CSS | |-------|-----| | .margin-{n} | margin: {n}px | | .margin-x-{n} | margin-left + margin-right: {n}px | | .margin-y-{n} | margin-top + margin-bottom: {n}px | | .margin-top-{n} | margin-top: {n}px | | .margin-right-{n} | margin-right: {n}px | | .margin-bottom-{n} | margin-bottom: {n}px | | .margin-left-{n} | margin-left: {n}px | | .margin-auto | margin: auto | | .margin-x-auto | margin-left: auto; margin-right: auto | | .margin-y-auto | margin-top: auto; margin-bottom: auto | | .margin-{top\|right\|bottom\|left}-auto | single side auto | | .padding-{n} | padding: {n}px | | .padding-x-{n} | padding-left + padding-right: {n}px | | .padding-y-{n} | padding-top + padding-bottom: {n}px | | .padding-top-{n} | padding-top: {n}px | | .padding-right-{n} | padding-right: {n}px | | .padding-bottom-{n} | padding-bottom: {n}px | | .padding-left-{n} | padding-left: {n}px |

<div class="padding-x-24 padding-x-16-sm margin-bottom-32 margin-bottom-16-sm">...</div>

Size

Same 4pt grid for fixed pixel sizes. Responsive: yes

Pixel values: 0, 2, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 80, 100, 120, 140

| Class | CSS | |-------|-----| | .size-{n}px | width: {n}px; height: {n}px | | .width-{n}px | width: {n}px | | .height-{n}px | height: {n}px | | .icon-{n} | font-size: {n}px (for icon fonts) |

Percentage values: 0, 10, 20, 25, 30, 40, 50, 60, 70, 75, 80, 90, 100

| Class | CSS | |-------|-----| | .width-{n} | width: {n}% | | .height-{n} | height: {n}% | | .width-1-3 / .width-2-3 | width: 33.33% / width: 66.66% | | .height-1-3 / .height-2-3 | height: 33.33% / height: 66.66% | | .width-100v / .height-100v | width: 100vw / height: 100vh | | .width-auto / .height-auto | width: auto / height: auto | | .min-width-0 / .min-width-100 | min-width: 0 / min-width: 100% | | .max-width-0 / .max-width-100 | max-width: 0 / max-width: 100% | | .min-height-0 / .min-height-100 | min-height: 0 / min-height: 100% | | .max-height-0 / .max-height-100 | max-height: 0 / max-height: 100% | | .min-width-100v / .max-width-100v | viewport units | | .min-height-100v / .max-height-100v | viewport units |


Typography

Heading and body size utilities. Apply to any element. Responsive: yes

| Class | font-size | line-height | |-------|-----------|-------------| | .h1 | 40px | 44px | | .h2 | 32px | 36px | | .h3 | 28px | 32px | | .h4 | 24px | 28px | | .h5 | 20px | 24px | | .h6 | 18px | 22px | | .text-body-1 | 16px | 24px | | .text-body-2 | 14px | 20px | | .text-body-3 | 12px | 16px | | .text-body-4 | 10px | 16px |

Alignment - Responsive: yes

.text-left .text-center .text-right .text-justify

Wrapping - Responsive: yes

.text-wrap .text-nowrap .text-ellipsis

Decoration - Responsive: yes

.text-line-none .text-strike .text-underline .hover-text-underline .hover-text-line-none .hover-text-strike

Weight - Responsive: yes

.font-weight-100 through .font-weight-800, .font-weight-normal, .font-weight-bold

Transform / Style - Responsive: no

.text-uppercase .text-lowercase .text-capitalize .font-style-italic .font-style-normal

Other - Responsive: no

.line-height-1 .letter-spacing-2 .cursor-pointer .cursor-default .cursor-progress .cursor-not-allowed .ul-square .ul-unstyled

<h2 class="h4 h5-sm font-weight-bold">Responsive heading size</h2>
<p class="text-body-2 text-body-3-sm">Smaller on mobile</p>

Flex

Responsive: yes

Container

.flex .inline-flex

Direction

.flex-row .flex-column .flex-row-reverse .flex-column-reverse

Wrap

.flex-wrap .flex-nowrap

Justify Content (main axis)

.flex-justify-start .flex-justify-end .flex-justify-center .flex-justify-between .flex-justify-around

Align Items (cross axis)

.flex-items-start .flex-items-end .flex-items-center .flex-items-baseline .flex-items-stretch

Align Content (multi-row)

.flex-content-start .flex-content-end .flex-content-center .flex-content-between .flex-content-around .flex-content-stretch

Gutter - values: 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40

.flex-gutter-{n} .flex-gutter-x-{n} .flex-gutter-y-{n}

Gutter uses negative margin on the container + padding on children. Don't add padding to children separately.

Align Self (on children)

.align-auto .align-start .align-end .align-center .align-baseline .align-stretch

Grow / Shrink

.grow-0 .grow-1 .shrink-0 .shrink-1

Fill

.fill: flex: 1 1 auto (fills available space) .equal: flex: 1 0 0% (equal width with siblings)

Order

.order-min-1 .order-0 .order-1 .order-2 .order-3 .order-4 .order-5

<div class="flex flex-justify-between flex-items-center flex-gutter-16 flex-column-sm">
  <div class="grow-1">...</div>
  <div class="shrink-0">...</div>
</div>

Border

Responsive: yes

| Class | CSS | |-------|-----| | .border | border: 1px solid currentColor | | .border-x | left + right | | .border-y | top + bottom | | .border-top | border-top: 1px solid currentColor | | .border-right | border-right: 1px solid currentColor | | .border-bottom | border-bottom: 1px solid currentColor | | .border-left | border-left: 1px solid currentColor | | .border-none | border: none | | .border-width-2 / -3 / -4 | border-width: 2px / 3px / 4px |

Border color follows currentColor. Set color on the element and the border matches automatically. Source users can replace currentColor directly in core/_border.scss with any value: a CSS variable, a hex, a token from their own system.


Border Radius

Responsive: no

Values: 0, 4, 8, 16, 9999

| Class | CSS | |-------|-----| | .rounded-{n} | border-radius: {n}px | | .circle | border-radius: 50% |

9999 is the pill value, fully rounded ends on any element regardless of height. Base only, no individual corners, no responsive variants.


Opacity

Responsive: yes

.opacity-02 .opacity-04 .opacity-06 .opacity-08 .opacity-1


Overflow

Responsive: yes

.overflow-auto .overflow-x-auto .overflow-y-auto .overflow-hidden .overflow-x-hidden .overflow-y-hidden .overflow-visible .overflow-x-visible .overflow-y-visible .overflow-scroll-hidden


Positioning

Responsive: yes

Display: .block .inline-block .inline

Position: .static .relative .absolute .fixed .sticky

Offset: .top-0 .right-0 .bottom-0 .left-0 .top-100 .right-100 .bottom-100 .left-100 .top-auto .right-auto .bottom-auto .left-auto

Center shortcuts: .center-x (horizontal) .center-y (vertical) .center (both)

Z-index: .z-min-2 .z-min-1 .z-0 .z-1 .z-2 .z-3 .z-4 .z-5 .z-6 .z-7 .z-8 .z-9 .z-max .z-unset


Shadow

Responsive: yes

| Class | | |-------|-| | .shadow-none | removes shadow | | .shadow-1 .shadow-2 .shadow-3 .shadow-4 | increasing elevation | | .hover-shadow-1 through .hover-shadow-4 | on :hover | | .active-shadow-1 through .active-shadow-4 | on :active |


Hidden

Breakpoint suffix = that size and below (max-width, same as all utilities). -greater suffix = that size and above (min-width, exception for visibility control only).

| Class | Applies when | |-------|--------------| | .hidden | always | | .hidden-sm | ≤ 640px | | .hidden-md | ≤ 768px | | .hidden-lg | ≤ 1024px | | .hidden-sm-greater | ≥ 640px | | .hidden-md-greater | ≥ 768px | | .hidden-lg-greater | ≥ 1024px | | .hidden-print | when printing | | .hidden-screen | when on screen |

<!-- Show on mobile only -->
<div class="hidden-sm-greater">Mobile only</div>

<!-- Show on desktop only -->
<div class="hidden-sm">Desktop only</div>

No .visible-* class exists or is needed. Use hidden on the element that should disappear, not on the one that should appear.


Scroll Locked

Locks body scroll. Use for modals, drawers, overlays.

<body class="scroll-locked">...</body>

Container

Centered, max-width containers.

| Class | Max-width | |-------|-----------| | .container-1320 | 1320px | | .container-960 | 960px |


Button

Structural base only. No color, no size, no padding. Those are yours.

<button class="button">Label</button>
<a class="button" href="#">Link styled as button</a>

Provides: display: inline-block, text-align: center, text-decoration: none, user-select: none, background-color: transparent, border: 1px solid currentColor, border-radius: 4px, cursor: pointer, disabled state.

border: 1px solid currentColor means the border always matches the element's color. Set color on the button and the border follows, no separate override needed. On a blank page this renders as black (browser default), which is correct and intentional.

Your team defines size, color, shape, and variants.


Form

Structural base for form controls. No color, no visual opinion.

| Element | Class | |---------|-------| | Text input | .form-text | | Textarea | .form-textarea | | Select / Dropdown | .form-dropdown | | Checkbox (stroke style) | .form-checkbox.form-checkbox-stroke | | Checkbox (fill style) | .form-checkbox.form-checkbox-fill | | Radio (stroke style) | .form-radio.form-radio-stroke | | Radio (fill style) | .form-radio.form-radio-fill | | Toggle | .form-toggle |

<input class="form-text" type="text" placeholder="Text input" />
<div class="form-checkbox form-checkbox-stroke">
  <label><input type="checkbox" /> Option</label>
</div>

Table

| Class | Description | |-------|-------------| | .table | Base table | | .table.table-compact | Reduced cell padding | | .table.table-bordered | All cell borders | | .table.table-border-none | No borders | | .table.table-striped | Alternating row background | | .table.table-hovered | Row highlight on hover | | .table .th-shrink | Column shrinks to content width | | .table .th-expand | Column expands to fill available space |


Skeleton

Apply .skeleton to leaf elements inside the component being loaded, not a separate skeleton component.

<!-- The same ArticleCard component, in loading state -->
<div class="article-card">
  <div class="skeleton article-card__image"></div>
  <div class="skeleton article-card__title"></div>
  <div class="skeleton article-card__body"></div>
</div>

Why not a separate skeleton component? A separate ArticleCardSkeleton has a different DOM structure than the real ArticleCard. When real content loads and the skeleton swaps out, the layout shifts, which is a CLS (Cumulative Layout Shift) regression. Using .skeleton on the same component structure guarantees an identical layout, so the shift is zero.

Skeleton exposes two CSS custom properties:

| Property | Default | Description | |---|---|---| | --skeleton-bg | #E7E7E7 | Base background color | | --skeleton-shimmer | rgba(255,255,255,0.35) | Shimmer highlight, expects an rgba value |

:root {
  --skeleton-bg: #D8D8D8;
  --skeleton-shimmer: rgba(255, 255, 255, 0.5);
}

Source users can override $skeleton-animation-duration (default 3s) before import.


Animate

Usage pattern: attach .animate as a base, then add animation and modifier classes:

<div class="animate fade-in-up delay-1s">...</div>

Animation types: fade-in, fade-in-down, fade-in-left, fade-in-right, fade-in-up, fade-out, fade-out-down, fade-out-left, fade-out-right, fade-out-up, slide-in-down, slide-in-left, slide-in-right, slide-in-up, slide-out-*, zoom-in, zoom-out

Modifiers: .repeat-1 .repeat-2 .repeat-3 / .delay-1s .delay-2s .delay-3s / .speed-02s .speed-04s .speed-06s .speed-08s .speed-2s .speed-3s


Group-Show

Behavioral utility. Shows child elements conditionally when a parent is hovered or focused.

<div class="group">
  <button>Hover me</button>
  <div class="group-hover-show">Visible only on parent hover</div>
</div>

Classes: .group-hover-show .group-focus-show .group-show (hover + focus)

Two caveats: .group-focus-show uses :not(:focus). For this to work on a non-interactive element like a div, add tabindex="0". And because visibility is toggled with display:none, it cannot be CSS-transitioned, so combining with .animate will not produce a fade-in.


Contributing

Technoart is deliberately opinionated. PRs that add utility values, breakpoints, or configuration options are unlikely to be merged. The constraint is the product.

Bug fixes, documentation improvements, and demo page additions are welcome.


License

MIT