Neoconstruct's ruler-and-compass marks: wedge, slash, disc, ring, plus, square, bar and star, plus a zero-padded numeral. Ships in the Neoconstruct theme.
import * as React from "react"
import { cn } from "cn"
/**
* Constructivist marks, drawn with ruler and compass. Each one comes from the
* period's own vocabulary: Lissitzky's wedge into a circle, the diagonal band,
* Suprematist disc and square, the arrow, the compass-drawn target, the
* printer's registration mark and the heavy typographic rule.
* Numerals live in `MarkerNumeral`.
*/
const shapes = {
// A wedge driving into a ring.
wedge: (
<>
<circle
cx="15"
cy="12"
r="6.5"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
/>
<polygon points="1,6.5 16,12 1,17.5" />
</>
),
// A thick band at 30 degrees.
band: <polygon points="2,15.5 19,5.5 22,10 5,20" />,
disc: <circle cx="12" cy="12" r="8" />,
// Two compass-drawn rings around a dot.
target: (
<>
<circle
cx="12"
cy="12"
r="9"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
/>
<circle cx="12" cy="12" r="3.5" />
</>
),
// The printer's registration mark: a cross through a ring.
registration: (
<>
<circle
cx="12"
cy="12"
r="6.5"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
/>
<path d="M11 1h2v22h-2zM1 11h22v2H1z" />
</>
),
arrow: <path d="M2 10h12V5l8 7-8 7v-5H2z" />,
square: <rect x="5" y="5" width="14" height="14" />,
bar: <rect x="2" y="9" width="20" height="6" />,
} as const
type MarkerVariant = keyof typeof shapes
/** A marker choice for components: any shape, or "none" to render nothing. */
type MarkerProp = MarkerVariant | "none"
/**
* A small Constructivist mark for buttons, badges and list items: a wedge,
* band, disc, target, registration mark, arrow, square or bar. It draws in
* the text color at `size-4` and is hidden from assistive tech.
*/
function Marker({
variant = "wedge",
className,
...props
}: React.ComponentProps<"svg"> & {
/** Which mark to draw; "none" renders nothing. Defaults to "wedge". */
variant?: MarkerProp
}) {
if (variant === "none") return null
return (
<svg
data-slot="marker"
data-theme="neoconstruct"
data-variant={variant}
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
className={cn("size-4 shrink-0", className)}
{...props}
>
{shapes[variant]}
</svg>
)
}
/**
* A zero-padded step or issue number in the heading font: 1 shows as "01".
* It has no size of its own, so set one with `className`. It is hidden from
* assistive tech, so pair it with text that names the step.
*/
function MarkerNumeral({
n,
className,
...props
}: React.ComponentProps<"span"> & {
/** The number to show, padded to two digits. */
n: number
}) {
return (
<span
data-slot="marker-numeral"
data-theme="neoconstruct"
aria-hidden="true"
className={cn("font-heading leading-none tabular-nums", className)}
{...props}
>
{String(n).padStart(2, "0")}
</span>
)
}
export { Marker, MarkerNumeral }
export type { MarkerVariant, MarkerProp }
pnpm dlx shadcn add @01orbital/neoconstruct-markernpx shadcn add @01orbital/neoconstruct-markeryarn shadcn add @01orbital/neoconstruct-markerbunx --bun shadcn add @01orbital/neoconstruct-markerFree under the MIT license. Add the @01orbital registry to components.json once (install guide), then run the command.
It lands at components/neoconstruct/neoconstruct-marker.tsx, as source you own and edit.
import {
Marker,
MarkerNumeral,
type MarkerVariant,
} from "@/components/neoconstruct/neoconstruct-marker"
const variants = [
"wedge",
"band",
"disc",
"target",
"registration",
"arrow",
"square",
"bar",
] as const satisfies readonly MarkerVariant[]
export default function MarkerDemo() {
return (
<div className="flex flex-col gap-8">
<div className="flex flex-wrap justify-center gap-6">
{variants.map((variant) => (
<div key={variant} className="flex w-16 flex-col items-center gap-2">
<Marker variant={variant} className="size-8 text-main-text" />
<span className="font-heading text-xs tracking-wider uppercase">
{variant}
</span>
</div>
))}
</div>
<div className="flex items-end gap-6">
{[1, 2, 3].map((n) => (
<div key={n} className="flex items-end gap-2">
<MarkerNumeral n={n} className="text-5xl text-main-text" />
<span className="font-heading text-sm tracking-wider uppercase">
Step
</span>
</div>
))}
</div>
</div>
)
}Neoconstruct Marker is built from 2 parts. In the example they nest like this:
Marker
MarkerNumeralMarker: A small Constructivist mark for buttons, badges and list items: a wedge, band, disc, target, registration mark, arrow, square or bar. It draws in the text color at size-4 and is hidden from assistive tech.MarkerNumeral: A zero-padded step or issue number in the heading font: 1 shows as "01". It has no size of its own, so set one with className. It is hidden from assistive tech, so pair it with text that names the step.A small Constructivist mark for buttons, badges and list items: a wedge, band, disc, target, registration mark, arrow, square or bar. It draws in the text color at size-4 and is hidden from assistive tech.
Prop
Type
It also accepts the HTML attributes of the element it renders.
A zero-padded step or issue number in the heading font: 1 shows as "01". It has no size of its own, so set one with className. It is hidden from assistive tech, so pair it with text that names the step.
Prop
Type
It also accepts the HTML attributes of the element it renders.
npm: cn. The CLI installs them for you.
Registry pieces it installs with it:
font-unboundedNeoconstruct Button
Neoconstruct button: hard offset shadow that presses in on hover, default, neutral, outline, ghost and reverse variants, optional mark, arrows, rotation and stripe, plus a ribbon button. Ships in the Neoconstruct theme.
Neoconstruct Wedge Orb
Poster art: a painted orb pierced by a red wedge on a diagonal black field, after El Lissitzky. Takes any Orb Palettes colorway; drop it into the Neoconstruct Hero as artwork.