technoprodev / technoart
No more custom CSS - All in one CSS framework for developing clean, professional & responsive web app.
This package is not auto-updated.
Last update: 2026-02-24 23:26:47 UTC
README
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
2is 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% |
9999is 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