motiv v0.2.0

Style 03 · motiv

Quiet surfaces for the screens people use all day.

Clean cards, pill buttons, a green you can swap in one line. The release screen below is plain server-rendered HTML: no client JavaScript at all, because nothing on it needs any.

Release 2.4 is ready

Built 6 minutes ago from main · 128 checks passed

Ship it to every region at once, or start with one and watch the numbers first.

Frankfurt

Live for 41 days

p50 latency 18 ms

Zürich

Live for 12 days

p50 latency 22 ms

Kyiv

Rolling out

p50 latency 31 ms
Read the whole screen, every line
import { Button, buttonClassName, Card, CardBody, CardFooter, CardHeader } from '@driif/motiv';

const regions = [
  { name: 'Frankfurt', status: 'Live for 41 days', latency: '18 ms' },
  { name: 'Zürich', status: 'Live for 12 days', latency: '22 ms' },
  { name: 'Kyiv', status: 'Rolling out', latency: '31 ms' },
];

export function MotivRelease() {
  return (
    <div className="site-release">
      <Card as="section" padding="lg" className="site-release__lead" aria-label="Release 2.4">
        <CardHeader
          title="Release 2.4 is ready"
          titleAs="h2"
          description="Built 6 minutes ago from main · 128 checks passed"
        />
        <CardBody>
          Ship it to every region at once, or start with one and watch the numbers first.
        </CardBody>
        <CardFooter>
          <a href="#button" className={buttonClassName({ variant: 'ghost' })}>
            Changelog
          </a>
          <Button variant="outline">Canary first</Button>
          <Button>Deploy everywhere</Button>
        </CardFooter>
      </Card>
      {regions.map((region) => (
        <Card key={region.name} as="article" variant="outlined" padding="sm">
          <CardHeader title={region.name} titleAs="h3" description={region.status} />
          <CardBody>p50 latency {region.latency}</CardBody>
        </Card>
      ))}
    </div>
  );
}

Getting started

Three steps, then make it yours.

  1. Install

    React 19 and react-dom are the only peers.

    pnpm add @driif/motiv
  2. Import the stylesheet once

    Add base.css too if you want a minimal reset.

    import '@driif/motiv/styles.css';
  3. Render something

    Named exports, no provider. Works in an Astro page as is.

    import '@driif/motiv/styles.css';
    import { Button, Card, CardBody, CardHeader } from '@driif/motiv';
    
    export function Welcome() {
      return (
        <Card>
          <CardHeader title="Welcome back" />
          <CardBody>
            <Button>Continue</Button>
          </CardBody>
        </Card>
      );
    }
  4. Re-theme with tokens

    All library CSS sits in @layer motiv.*, so your unlayered rules win without !important.

    :root {
      --motiv-color-primary: #2563eb;
      --motiv-color-on-primary: #ffffff;
      --motiv-radius: 0.25rem;
    }

Button

Five variants, three sizes, a loading state. Links borrow its look.

A link, dressed up
import { Button, buttonClassName } from '@driif/motiv';

export function ButtonDemo() {
  return (
    <>
      <Button>Deploy</Button>
      <Button variant="secondary">Schedule</Button>
      <Button variant="outline">Preview</Button>
      <Button variant="danger" size="sm">
        Delete
      </Button>
      <Button loading>Saving</Button>
      <a href="#card" className={buttonClassName({ variant: 'ghost' })}>
        A link, dressed up
      </a>
    </>
  );
}

Card

A surface with a header, a body and a footer for actions.

Users

Active this week

128 people signed in at least once during the last seven days.
import { Button, Card, CardBody, CardFooter, CardHeader } from '@driif/motiv';

export function CardDemo() {
  return (
    <Card style={{ maxWidth: '24rem' }}>
      <CardHeader title="Users" description="Active this week" />
      <CardBody>128 people signed in at least once during the last seven days.</CardBody>
      <CardFooter>
        <Button variant="outline" size="sm">
          Export
        </Button>
        <Button size="sm">View all</Button>
      </CardFooter>
    </Card>
  );
}

cx

The class-name joiner the components use, exported for yours.

className="note"
import { Button, cx } from '@driif/motiv';
import { useState } from 'react';

export function CxDemo() {
  const [urgent, setUrgent] = useState(false);
  const className = cx('note', urgent && 'note--urgent', null);
  return (
    <>
      <Button variant="outline" aria-pressed={urgent} onClick={() => setUrgent(!urgent)}>
        Urgent
      </Button>
      <code>className="{className}"</code>
    </>
  );
}