Inkvelle - Case Study

Every project starts the same way. You pick a Google Font, paste the <link> tag, write some heading styles, and by the time the project ships you've got four different files that all touch typography in slightly different ways.

inkvelle started as a fix for that specific problem — a single <Typography> component that handles variants, Google Fonts injection, and hero entrance animations, without pulling in Framer Motion or GSAP as peer dependencies.

Installation

BASH
npm install inkvelle
# or
yarn add inkvelle

Zero peer dependencies for the animation and font systems. Drops into any React project, including Next.js App Router, without configuration.

Core Idea

The variant prop maps to both a rendered HTML tag and a visual scale. You declare what the text is, not how it should look.

TYPESCRIPT
import { Typography } from "inkvelle";

<Typography variant="Overline" color="#6366f1">New Feature</Typography>
<Typography variant="Display" font="Bricolage Grotesque" animation="clip">
  Build faster with <em>types</em>
</Typography>

The font prop accepts any Google Font name as a plain string. Internally, useInsertionEffect injects the stylesheet before the browser paints — no manual imports, no @import in your CSS, no next/font configuration.

Animation System

Animations are layered across three tiers, each overriding the previous.

Named presets — 30+ built-in entrance animations for Display and H1 variants.

TYPESCRIPT
<Typography variant="Display" font="Bricolage Grotesque" animation="velvet">
  The future of design
</Typography>

motionConfig — Write your own CSS keyframe body. Works on any variant, with optional per-word or per-character stagger.

TYPESCRIPT
<Typography
  variant="H2"
  motionConfig={{
    keyframes: `from { opacity: 0; transform: translateY(24px) skewX(6deg); }
                to   { opacity: 1; transform: none; }`,
    duration: "0.8s",
    split: "words",
    staggerDelay: 0.09,
  }}
>
  Section heading
</Typography>

motionRef — A ref callback that fires after mount, giving you the raw DOM element for GSAP, Framer Motion, or the Web Animations API.

TYPESCRIPT
<Typography
  variant="Display"
  motionRef={(el) => {
    if (!el) return;
    gsap.from(el, { opacity: 0, y: 40, duration: 0.9, ease: "power3.out" });
  }}
>
  Full control
</Typography>

Priority order: motionRef > motionConfig > animation. All animations use only transform, opacity, and filter — GPU-composited, 60fps safe.

Italic Accent

When italic={true}, any <em> inside a Display or H1 renders in Instrument Serif italic with a configurable accent color. Off by default.

TYPESCRIPT
<Typography variant="Display" font="Bricolage Grotesque" italic accentColor="#6366f1">
  Build with <em>intention</em>
</Typography>

Global Defaults with TypographyProvider

Wrap your app with TypographyProvider to set defaults once. Direct props always win — the provider is a fallback, not an override.

TYPESCRIPT
<TypographyProvider
  theme={{ font: "Bricolage Grotesque", accentColor: "#6366f1", animation: "rise" }}
>
  <Typography variant="Display">Build with <em>intention</em></Typography>
  <Typography variant="H1" animation="clip">Overrides animation only</Typography>
</TypographyProvider>

Reflections

The split-text logic for motionConfig works well for common cases but becomes fragile with deeply nested inline elements — a proper AST traversal would handle this more robustly. A scroll-trigger option (triggerOnScroll) is also on the roadmap, since most landing pages want animations to fire on viewport entry rather than on mount.

Overall, the constraint of keeping the API surface small and the install frictionless shaped most of the decisions here. It's a package I've reached for on every project since building it, which is usually the sign that an abstraction landed at the right level.

Available on npm. Install with

TYPESCRIPT
npm install inkvelle

Read complete Docs at inkvelle.edwinvakayil.info , check Source Code

TypeScriptFramer MotionGsap