Skip to content

Kinetic Text

A text component that animates the font weight of characters on hover.

Installation

Copy and paste the following code into your project:

vue
<script setup lang="ts">
import { computed } from "vue";
import { cn } from "@/lib/utils";

type As = "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "p" | "span";

interface KineticTextProps {
  text: string;
  as?: As;
  class?: string;
  style?: string | Record<string, string>;
}

const props = withDefaults(defineProps<KineticTextProps>(), {
  as: "h1",
});

const letters = computed(() =>
  props.text.split("").map((letter) => (letter === " " ? " " : letter)),
);
</script>

<template>
  <component
    :is="props.as"
    :class="cn('kinetic-text flex flex-wrap font-[300]', props.class)"
    :style="props.style"
  >
    <span
      v-for="(letter, i) in letters"
      :key="i"
      aria-hidden="true"
      class="kinetic-text__char"
      >{{ letter }}</span
    >
    <span class="sr-only">{{ props.text }}</span>
  </component>
</template>

<style scoped>
.kinetic-text {
  --hover-padding: calc(1em / 12);
  --text-stroke-width: calc(1em * 125 / 6000);
}

.kinetic-text__char {
  will-change: font-weight, -webkit-text-stroke-width, padding;
  -webkit-text-stroke-color: transparent;
  -webkit-text-stroke-width: var(--text-stroke-width);
  transition:
    font-weight 0.4s,
    -webkit-text-stroke-color 0.4s,
    padding 0.4s;
}

/* Hovered character: heaviest weight + visible stroke + padding */
.kinetic-text__char:hover {
  padding-inline: var(--hover-padding);
  font-weight: 900;
  -webkit-text-stroke-color: currentColor;
  -webkit-text-stroke-width: calc(var(--text-stroke-width) * 2);
}

/* The character immediately after the hovered one */
.kinetic-text__char:hover + .kinetic-text__char {
  padding-inline: var(--hover-padding);
  font-weight: 600;
}

/* The character two positions after the hovered one */
.kinetic-text__char:hover + .kinetic-text__char + .kinetic-text__char {
  font-weight: 400;
}

/* The character immediately before the hovered one */
.kinetic-text__char:has(+ .kinetic-text__char:hover) {
  padding-inline: var(--hover-padding);
  font-weight: 600;
}

/* The character two positions before the hovered one */
.kinetic-text__char:has(+ .kinetic-text__char + .kinetic-text__char:hover) {
  font-weight: 400;
}
</style>

Usage

vue
<script setup lang="ts">
import KineticText from "@/components/ui/kinetic-text.vue";
</script>

<template>
  <KineticText text="some text" />
</template>

Props

PropTypeDefaultDescription
textstring-The text content to render with the kinetic effect.
as"h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "p" | "span""h1"The HTML element to render as.
classstring-Additional class names for the wrapper element.

Credits

Released under the MIT License.