Component reference

Components

Four web components — custom elements built with Lit, framework-agnostic, TypeScript-first. Each component has a named entry point for precise imports. The Rating object is the shared data contract between them.

Getting started

npm install affect-kit

Imports

Each component has its own entry point. Import only what you need:

import 'affect-kit/rater';   // registers <affect-kit-rater>
import 'affect-kit/result';  // registers <affect-kit-result>
import 'affect-kit/face';    // registers <affect-kit-face>
import 'affect-kit/compare'; // registers <affect-kit-compare>

// Or register all four at once:
import 'affect-kit';

TypeScript types

import type { Rating, EmotionLabel, EmotionName } from 'affect-kit';
import { createRating, averageRatings }           from 'affect-kit';

Quick wiring: rater → result

<affect-kit-rater id="rater"></affect-kit-rater>
<affect-kit-result id="result" show-face show-labels color-mode></affect-kit-result>

<script type="module">
  import 'affect-kit/rater';
  import 'affect-kit/result';

  const rater  = document.querySelector('affect-kit-rater');
  const result = document.querySelector('affect-kit-result');
  rater.addEventListener('change', e => result.rating = e.detail);
</script>

React

Custom elements work in React 19+ with native support, or React 18 with a thin ref wrapper. TypeScript types are available via the global HTMLElementTagNameMap augmentation shipped in each component entry point.

// React 19+
import 'affect-kit/rater';

function App() {
  return (
    <affect-kit-rater
      color-mode
      onchange={(e) => console.log(e.detail)}
    />
  );
}

2. <affect-kit-rater>

The primary capture interface. The user drags on a V/A pad to set a gut-feeling position — the face glyph and background color update in real time. On release, 55 emotion chips from the NRC VAD Lexicon rearrange by proximity to the pad position. The user taps chips to intensity-rate them at 1, 2, or 3. Every pad release and chip toggle after the first placement fires a change event with a Rating object.

color-mode

Attributes / props

AttributeTypeDefaultDescription
color-mode 'background' | 'words' | null null How V/A color is applied. 'background' tints the surface; chips adapt their contrast. 'words' paints each chip with its own NRC lexicon color (rainbow constellation). Legacy boolean usage (<... color-mode>) maps to 'background'.
animated boolean true Enables breath + tremor animation on the face glyph. Always respects prefers-reduced-motion: reduce. Set animated="false" in HTML to opt out.
show-vad boolean false Shows a debug readout of the current V / A / D values below the chip list.
submit-label string 'Done' Label for the commit button shown once at least one chip is selected.

Events

EventDetail typeWhen it fires
change Rating On every pointer release from the pad, and on every chip toggle after the first pad placement. Not fired on the initial drag before first release.
commit Rating Fired once when the user taps the Done button. Use this as the explicit "I'm finished" signal rather than wiring to every intermediate change. Requires at least one chip to be selected (the button is hidden otherwise).

Methods

MethodSignatureDescription
setRating() (rating: Rating) => void Programmatically pre-fills the rater from a Rating object. Reveals chips immediately. Useful for editing a previously captured rating.
reset() () => void Clears all state back to the initial empty position — pad at center, no chips selected, face neutral.

CSS custom properties

PropertyDefaultDescription
--affect-kit-rater-max-width 640px Maximum width of the widget. Override to fit narrow or wide containers.

3. <affect-kit-result>

A display panel for a captured Rating. Renders the selected emotion words scaled by intensity (level 3 is largest, level 1 is smallest), an optional face glyph, and an optional color tint. Designed to pair with <affect-kit-rater> but can also be driven from stored data. The face and color always follow rating.face (the pre-verbal pad gesture), not the composite label coordinates — preserving the visual signal of the gut feeling even when labels shift the V/A/D values.

color-mode

← Rate something on the rater above to see it here, or this is pre-seeded.

Attributes / props

Attribute / propTypeDefaultDescription
rating prop only Rating | null null The rating to display. Set via JavaScript property — not an HTML attribute. The component renders nothing when null.
show-face boolean false Show the face glyph. Face shape and color follow rating.face, not the composite label V/A.
show-labels boolean true Show the selected emotion words, scaled continuously by intensity level.
color-mode 'background' | 'words' | null null 'background' paints a V/A glow on the panel; 'words' colors each word by its own NRC lexicon V/A (background stays neutral — useful when averaging many ratings would wash out a single tint). Legacy boolean attribute maps to 'background'.
show-vad boolean false Show the raw V / A / D readout (debug).
align 'left' | 'center' | 'right' 'center' Word alignment within the panel.
variant 'default' | 'compact' 'default' Sizing preset. 'compact' reduces padding for tight layouts.
animated boolean true Enables breath animation on the face glyph. Respects prefers-reduced-motion.

CSS custom properties

PropertyDefaultDescription
--affect-kit-result-max-width 640px Maximum width of the widget.
--affect-kit-font-size 1rem Base font size. Every internal size is em-relative to this — scale the whole widget with one variable.
--affect-kit-ink #1a1a1a Ink color for emotion words.

Layout custom properties

These pierce the shadow DOM and let a parent container (like <affect-kit-compare>) drive the inner face/words layout via container queries without JS:

PropertyControls
--_face-dirflex-direction of the content row
--_face-alignalign-items of the content row
--_face-gapGap between face and words
--_face-stepWord size growth per intensity level
--_face-mbFace zone margin-bottom
--_face-mtFace zone margin-top

4. <affect-kit-face>

A standalone animated face glyph driven directly by v (valence) and a (arousal) props. Runs a 60 fps animation loop by default — breath (subtle scale oscillation) and tremor (position noise scaled by arousal). Size is set via CSS; the SVG fills its container. Used internally by both <affect-kit-rater> and <affect-kit-result>.

0.00
0.00

Attributes / props

Attribute / propTypeDefaultDescription
v number 0 Valence ∈ [−1, 1]. Drives brow angle, eye shape, and mouth curve. Clamped silently if out of range.
a number 0 Arousal ∈ [−1, 1]. Drives eye openness, animation tremor amplitude, and lip parting. Clamped silently.
animated boolean true Enables breath + tremor animation. Use animated="false" in HTML. Always respects prefers-reduced-motion: reduce regardless of this attribute.
motionScale prop only number 1 Scales all animation amplitude in [0, 1]. The rater sets this to ~0.2 while the user is actively dragging so the face stays readable, then restores to 1.0 on release.

Methods

MethodSignatureDescription
triggerShock() () => void Triggers a brief high-frequency shake. Called by <affect-kit-rater> internally when a high-arousal emotion chip is toggled up, so the face reacts to the selection.

Default size

The element defaults to 120 × 120px (set via :host styles). Override freely with CSS — the SVG fills 100% of its container:

affect-kit-face { width: 80px; height: 80px; }
/* or inline: */
<affect-kit-face style="width:200px;height:200px"></affect-kit-face>

5. <affect-kit-compare>

A paired side-by-side display for two ratings or two rating arrays. When color-mode is on, the card background is a gradient from the left rating's V/A color to the right rating's V/A color — the color transition itself tells the story. Layout responds to host width via container queries: side-by-side when wide, stacked when narrow. Each half's face sits on the outer edge so the faces frame the gradient. The widget makes no claims about what the comparison means — interpretation belongs to the user.

color-mode

Attributes / props

Attribute / propTypeDefaultDescription
beforeRating prop only Rating | Rating[] | null null Left side. A single Rating (rendered as-is) or a Rating[] (averaged via averageRatings()). Property is beforeRating because before is reserved on Element.
afterRating prop only Rating | Rating[] | null null Right side. Same shape as beforeRating.
before-label string 'Before' Caption above the left rating.
after-label string 'After' Caption above the right rating.
show-face boolean false Show face glyphs in each half. Forwarded to each inner <affect-kit-result>.
show-labels boolean true Show emotion words in each half.
color-mode 'background' | 'words' | null null 'background' paints a card gradient between the two ratings' V/A colors. 'words' drops the gradient and colors each label in each half by its own lexicon V/A — keeps individual labels distinct even when averaging many ratings would otherwise wash the gradient to gray. Legacy boolean attribute maps to 'background'.

CSS custom properties

PropertyDefaultDescription
--affect-kit-compare-max-width 880px Maximum width of the widget.

Averaging arrays

Pass a Rating[] to either side and the widget averages it automatically. Useful for windowed comparisons (e.g. last month vs last week):

import 'affect-kit/compare';
import { createRating } from 'affect-kit';

const cmp = document.querySelector('affect-kit-compare');
cmp.beforeRating = lastMonthRatings; // Rating[]
cmp.afterRating  = lastWeekRatings;  // Rating[]

6. TypeScript types

import type { Rating, EmotionLabel, EmotionName } from 'affect-kit';
import { createRating, averageRatings }           from 'affect-kit';

Rating

The central data object. Emitted by change and commit events, consumed by result and compare. Two VAD sources are preserved separately so researchers can use either or both:

interface Rating {
  /** Unix timestamp (ms) at commit time. */
  timestamp: number;

  /** Pad position that drives the face glyph and color. The pre-verbal gut gesture. */
  face: { v: number; a: number };

  /** Selected emotion words, each carrying its lexicon VAD coordinates. */
  labels: EmotionLabel[];

  /**
   * Intensity-weighted centroid of selected labels' VAD values.
   * Novel derivation grounded in affective theory — not independently validated.
   * null when no labels were selected.
   */
  composite: { v: number; a: number; d: number } | null;
}

// To get a single resolved VAD vector:
//   rating.composite ?? { v: rating.face.v, a: rating.face.a, d: 0 }

EmotionLabel

interface EmotionLabel {
  name:  EmotionName; // strict — must be in the validated vocabulary
  level: number;      // 1 | 2 | 3 from rater; widened to number for averaged data
  vad:   { v: number; a: number; d: number };
  // ^ Per-word NRC VAD Lexicon coordinates (Mohammad 2025).
  //   Pre-aggregate — direct lookup, identical for every rating that contains
  //   this name. Aggregation across labels happens only at Rating.composite.
}

createRating()

Construct a Rating from a face position and optional labels. Throws on any label name not in the validated vocabulary:

import { createRating } from 'affect-kit';

const r = createRating({
  face: { v: 0.5, a: 0.3 },  // pad position (required)
  labels: [                   // optional
    { name: 'calm',     level: 2 },
    { name: 'grateful', level: 1 },
  ],
});

averageRatings()

Average an array of Rating objects into a single Rating. Label levels are averaged continuously — the result widget renders averaged levels as continuous intermediate sizes:

import { averageRatings } from 'affect-kit';

const avg = averageRatings(sessionRatings); // Rating[] → Rating

7. React wrapper

@affect-kit/react wraps all four components using @lit/react. You get typed camelCase props, idiomatic onChange handlers, and correct property/attribute mapping — without writing any ref plumbing.

Install

npm install affect-kit @affect-kit/react

Import

import { Rater, Result, Face, Compare } from '@affect-kit/react';
import type { Rating } from 'affect-kit';

Usage

import { Rater, Result } from '@affect-kit/react';
import { useState } from 'react';
import type { Rating } from 'affect-kit';

export function RatingWidget() {
  const [rating, setRating] = useState<Rating | null>(null);

  return (
    <>
      <Rater colorMode onChange={(e) => setRating(e.detail)} />
      {rating && <Result rating={rating} showFace showLabels colorMode />}
    </>
  );
}

Props

All HTML attributes map to camelCase props (color-modecolorMode, show-faceshowFace, etc.). Complex properties (rating, beforeRating, afterRating) are passed directly as object props — no serialisation.

Events

ComponentReact propNative eventDetail type
Rater onChange change CustomEvent<Rating>
Rater onCommit commit CustomEvent<Rating>

Refs

Pass a ref to access the underlying element and call instance methods:

import { useRef } from 'react';
import { Rater } from '@affect-kit/react';
import type { AffectKitRater } from 'affect-kit/rater';

const ref = useRef<AffectKitRater>(null);
// ref.current.reset() — clears pad + chip selection