npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

axiom-embed

v0.1.0

Published

Embeddable Fitch-style proof editor Web Components

Readme

axiom-embed

Embeddable Fitch-style proof editor Web Components for formal logic education. Provides client-side validation of natural deduction proofs with SCORM compatibility for LMS integration.

Features

  • Web Components - Works with any framework (React, Vue, plain HTML)
  • Two Modes - Exercise mode (fixed goal) and Sandbox mode (free exploration)
  • Configurable Rules - Enable propositional, FOL, or custom rule subsets
  • SCORM 1.2/2004 - Full LMS integration with suspend_data and scoring
  • Smart Editing - Auto-indent, symbol conversion, rule autocomplete
  • Fully Themeable - CSS custom properties for complete visual customization

Installation

npm

npm install axiom-embed

CDN / Script Tag

<script src="https://unpkg.com/axiom-embed/dist/axiom-embed.umd.js"></script>

Building from Source

# Install dependencies
npm install

# Development build with watch mode
npm run dev

# Production build
npm run build

# Generate TypeScript declarations
npm run build:types

# Clean dist directory
npm run clean

# Run tests
npm test

# Full release build (clean + build + types)
npm run prepublishOnly

Build Outputs

| File | Format | Description | |------|--------|-------------| | dist/axiom-embed.js | ESM | ES module (requires fitch-js as peer dependency) | | dist/axiom-embed.umd.js | UMD | Standalone bundle (includes fitch-js, auto-injects styles) | | dist/axiom-embed.css | CSS | Extracted styles (for ESM usage) | | dist/index.d.ts | TypeScript | Type declarations |

Usage

UMD Bundle (Simplest)

The UMD bundle auto-initializes and injects styles. Just include the script and use the component:

<!DOCTYPE html>
<html>
<head>
  <script src="axiom-embed.umd.js"></script>
</head>
<body>
  <axiom-proof
    mode="exercise"
    premises='["P", "P -> Q"]'
    goal="Q"
    rules="propositional"
  ></axiom-proof>
</body>
</html>

ESM Module

When using as an ES module, you must initialize fitch-js manually and import the CSS:

import * as AxiomEmbed from 'axiom-embed';
import 'axiom-embed/css';
import * as fitchJS from 'fitch-js';

// Initialize before using components
AxiomEmbed.init(fitchJS);
<axiom-proof
  mode="exercise"
  premises='["P", "P -> Q"]'
  goal="Q"
></axiom-proof>

With Bundlers (Vite, Webpack, etc.)

// main.js
import * as AxiomEmbed from 'axiom-embed';
import 'axiom-embed/css';
import * as fitchJS from 'fitch-js';

AxiomEmbed.init(fitchJS);

With React

import { useEffect, useRef } from 'react';
import * as AxiomEmbed from 'axiom-embed';
import 'axiom-embed/css';
import * as fitchJS from 'fitch-js';

// Initialize once at app startup
AxiomEmbed.init(fitchJS);

function ProofEditor({ premises, goal, onComplete }) {
  const ref = useRef(null);

  useEffect(() => {
    const el = ref.current;
    const handleComplete = (e) => onComplete?.(e.detail);
    el?.addEventListener('axiom:complete', handleComplete);
    return () => el?.removeEventListener('axiom:complete', handleComplete);
  }, [onComplete]);

  return (
    <axiom-proof
      ref={ref}
      mode="exercise"
      premises={JSON.stringify(premises)}
      goal={goal}
      rules="propositional"
    />
  );
}

Component API

<axiom-proof> Attributes

| Attribute | Type | Default | Description | |-----------|------|---------|-------------| | mode | "exercise" | "sandbox" | "sandbox" | Exercise validates against a goal; sandbox is freeform | | premises | JSON array or CSV | [] | Premises for the proof, e.g. '["P", "P -> Q"]' | | goal | string | null | Target conclusion for exercise mode | | rules | preset, JSON array, or CSV | all rules | Allowed proof rules (see Rule Sets) | | theme | "light" | "dark" | "light" | Color theme | | readonly | boolean | false | Disable editing | | show-toolbar | boolean | false | Show symbol insertion toolbar | | problem-id | string | "default" | Unique ID for persistence | | prefill-premises | "true" | "false" | "true" | Auto-fill premises in editor | | scoring | preset or JSON | "completion" | Scoring configuration (see Scoring) | | scorm | boolean | false | Enable SCORM integration |

JavaScript API

const proof = document.querySelector('axiom-proof');

// Get/set content
proof.getValue();           // Returns current proof text
proof.setValue(content);    // Set proof text

// Validation
proof.validate();           // Validate and return result
proof.validate(true);       // Validate and count as attempt (affects scoring)
proof.isComplete();         // Check if proof is complete

// State management
proof.reset();              // Reset to initial state
proof.getSuspendData();     // Get serialized state for persistence
proof.setSuspendData(data); // Restore from serialized state

// Scoring
proof.getScore();           // Returns { raw, max, min }

Events

// Fired on each validation
proof.addEventListener('axiom:validate', (e) => {
  console.log(e.detail.isValid);           // boolean
  console.log(e.detail.issues);            // string[]
  console.log(e.detail.conclusionReached); // boolean
});

// Fired when proof is successfully completed
proof.addEventListener('axiom:complete', (e) => {
  console.log(e.detail.score);      // number (0-100)
  console.log(e.detail.attempts);   // number
  console.log(e.detail.elapsedMs);  // number
});

// Fired on content change
proof.addEventListener('axiom:change', (e) => {
  console.log(e.detail.content);  // string
  console.log(e.detail.isDirty);  // boolean
});

// Fired when component is initialized and ready
proof.addEventListener('axiom:ready', () => {
  console.log('Component ready');
});

CSS Customization

axiom-embed is fully customizable via CSS custom properties. You can style every aspect of the editor without touching source files.

CSS Custom Properties Reference

Override these properties on :root, a parent element, or directly on axiom-proof:

:root {
  /* ===== Core Colors ===== */
  --axiom-bg: #ffffff;              /* Editor background */
  --axiom-fg: #1a1a1a;              /* Primary text color */
  --axiom-bg-secondary: #f5f5f5;    /* Header/toolbar/status background */
  --axiom-border: #e0e0e0;          /* Border color */
  --axiom-border-focus: #3b82f6;    /* Focus ring color */

  /* ===== Gutter (Line Numbers) ===== */
  --axiom-gutter-bg: #fafafa;       /* Gutter background */
  --axiom-gutter-fg: #9ca3af;       /* Line number color */

  /* ===== Error Highlighting ===== */
  --axiom-line-error-bg: rgba(239, 68, 68, 0.1);  /* Error line background */
  --axiom-line-error-border: #ef4444;              /* Error line left border */

  /* ===== Syntax Highlighting ===== */
  --axiom-syn-op: #7c3aed;          /* Operators: ->, /\, \/, <->, ~, etc. */
  --axiom-syn-var: #0891b2;         /* Variables: P, Q, x, y */
  --axiom-syn-pred: #2563eb;        /* Predicates: F(x), G(x,y) */
  --axiom-syn-rule: #059669;        /* Rule names: ->E, /\I, Hyp, PR */
  --axiom-syn-cite: #d97706;        /* Line citations: 1, 2-4 */
  --axiom-syn-comment: #9ca3af;     /* Comments */
  --axiom-syn-depth: #d4d4d4;       /* Subproof depth markers (|) */
  --axiom-syn-paren: #6b7280;       /* Parentheses */
  --axiom-syn-colon: #6b7280;       /* Colons */

  /* ===== Autocomplete Dropdown ===== */
  --axiom-dropdown-bg: #ffffff;
  --axiom-dropdown-border: #e0e0e0;
  --axiom-dropdown-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  --axiom-dropdown-selected: #eff6ff;   /* Selected item background */
  --axiom-dropdown-hover: #f5f5f5;      /* Hovered item background */

  /* ===== Typography ===== */
  --axiom-font-mono: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, monospace;
  --axiom-font-ui: system-ui, -apple-system, sans-serif;
  --axiom-font-size: 14px;
  --axiom-line-height: 1.5;

  /* ===== Spacing & Shape ===== */
  --axiom-padding: 12px;
  --axiom-radius: 6px;
}

Styling Specific Instances

Apply styles to individual proof editors:

/* Target a specific editor by ID */
#my-custom-proof {
  --axiom-bg: #1a1a2e;
  --axiom-fg: #eaeaea;
  --axiom-syn-op: #ff6b6b;
  --axiom-font-size: 16px;
}

/* Target all editors in a container */
.proof-container axiom-proof {
  --axiom-radius: 12px;
  --axiom-border: #333;
}

Complete Theme Examples

High Contrast Theme

.high-contrast axiom-proof {
  --axiom-bg: #000000;
  --axiom-fg: #ffffff;
  --axiom-bg-secondary: #1a1a1a;
  --axiom-border: #ffffff;
  --axiom-border-focus: #ffff00;
  --axiom-gutter-bg: #0a0a0a;
  --axiom-gutter-fg: #cccccc;
  --axiom-syn-op: #ff6b6b;
  --axiom-syn-var: #4ecdc4;
  --axiom-syn-pred: #45b7d1;
  --axiom-syn-rule: #95e1d3;
  --axiom-syn-cite: #f9ca24;
  --axiom-line-error-bg: rgba(255, 0, 0, 0.3);
  --axiom-line-error-border: #ff0000;
  --axiom-dropdown-bg: #1a1a1a;
  --axiom-dropdown-selected: #333333;
}

Solarized Light Theme

.solarized-light axiom-proof {
  --axiom-bg: #fdf6e3;
  --axiom-fg: #657b83;
  --axiom-bg-secondary: #eee8d5;
  --axiom-border: #93a1a1;
  --axiom-gutter-bg: #eee8d5;
  --axiom-gutter-fg: #93a1a1;
  --axiom-syn-op: #cb4b16;
  --axiom-syn-var: #268bd2;
  --axiom-syn-pred: #2aa198;
  --axiom-syn-rule: #859900;
  --axiom-syn-cite: #b58900;
  --axiom-dropdown-bg: #fdf6e3;
  --axiom-dropdown-selected: #eee8d5;
}

Nord Theme

.nord axiom-proof {
  --axiom-bg: #2e3440;
  --axiom-fg: #eceff4;
  --axiom-bg-secondary: #3b4252;
  --axiom-border: #4c566a;
  --axiom-border-focus: #88c0d0;
  --axiom-gutter-bg: #3b4252;
  --axiom-gutter-fg: #4c566a;
  --axiom-syn-op: #b48ead;
  --axiom-syn-var: #88c0d0;
  --axiom-syn-pred: #81a1c1;
  --axiom-syn-rule: #a3be8c;
  --axiom-syn-cite: #ebcb8b;
  --axiom-syn-depth: #4c566a;
  --axiom-dropdown-bg: #3b4252;
  --axiom-dropdown-border: #4c566a;
  --axiom-dropdown-selected: #434c5e;
  --axiom-dropdown-hover: #434c5e;
}

Monokai Theme

.monokai axiom-proof {
  --axiom-bg: #272822;
  --axiom-fg: #f8f8f2;
  --axiom-bg-secondary: #3e3d32;
  --axiom-border: #49483e;
  --axiom-border-focus: #a6e22e;
  --axiom-gutter-bg: #3e3d32;
  --axiom-gutter-fg: #75715e;
  --axiom-syn-op: #f92672;
  --axiom-syn-var: #66d9ef;
  --axiom-syn-pred: #a6e22e;
  --axiom-syn-rule: #e6db74;
  --axiom-syn-cite: #fd971f;
  --axiom-syn-comment: #75715e;
}

Built-in Dark Theme

Use the theme="dark" attribute:

<axiom-proof theme="dark" mode="sandbox"></axiom-proof>

Or apply programmatically:

proof.setAttribute('theme', 'dark');

Dynamic Theme Switching

function setTheme(themeName) {
  const proof = document.querySelector('axiom-proof');
  proof.setAttribute('theme', themeName);
}

// Or using CSS classes
function applyCustomTheme(className) {
  document.body.className = className;  // e.g., 'nord', 'monokai'
}

Responsive Styling

/* Smaller font on mobile */
@media (max-width: 768px) {
  axiom-proof {
    --axiom-font-size: 12px;
    --axiom-padding: 8px;
  }
}

/* Larger font for accessibility */
@media (prefers-reduced-motion: reduce) {
  axiom-proof {
    --axiom-font-size: 18px;
  }
}

Rule Sets

Presets

| Preset | Rules Included | |--------|---------------| | propositional | PR, Hyp, R, ∧I, ∧E, ∨I, ∨E, →I, →E, ↔I, ↔E, ¬I, ¬E, ⊥I, ⊥E | | fol | All propositional + ∀I, ∀E, ∃I, ∃E, =I, =E, LEM | | structural | PR, Hyp, R |

Custom Rule Sets

<!-- Use a preset -->
<axiom-proof rules="propositional"></axiom-proof>

<!-- JSON array of specific rules -->
<axiom-proof rules='["PR", "Hyp", "∧I", "∧E", "→I", "→E"]'></axiom-proof>

<!-- Comma-separated (ASCII symbols work too) -->
<axiom-proof rules="PR, Hyp, /\I, /\E, ->I, ->E"></axiom-proof>

Available Rules

| Symbol | ASCII | Name | Category | |--------|-------|------|----------| | PR | - | Premise | Structural | | Hyp | - | Hypothesis (starts subproof) | Structural | | R | - | Reiteration | Structural | | ∧I | /\I | And Introduction | Propositional | | ∧E | /\E | And Elimination | Propositional | | ∨I | /I | Or Introduction | Propositional | | ∨E | /E | Or Elimination | Propositional | | →I | ->I | Conditional Introduction | Propositional | | →E | ->E | Modus Ponens | Propositional | | ↔I | <->I | Biconditional Introduction | Propositional | | ↔E | <->E | Biconditional Elimination | Propositional | | ¬I | ~I | Negation Introduction | Propositional | | ¬E | ~E | Negation Elimination | Propositional | | ⊥I | !?I | Contradiction Introduction | Propositional | | ⊥E | !?E | Explosion (Ex Falso) | Propositional | | ∀I | !AI | Universal Introduction | FOL | | ∀E | !AE | Universal Elimination | FOL | | ∃I | !EI | Existential Introduction | FOL | | ∃E | !EE | Existential Elimination | FOL | | =I | - | Identity Introduction | FOL | | =E | - | Identity Elimination | FOL | | LEM | - | Law of Excluded Middle | Meta |

Scoring

Modes

| Mode | Description | |------|-------------| | completion | 100 points on completion, 0 otherwise (default) | | attempts | Base score minus penalty per attempt after first | | none | Scoring disabled |

Configuration

<!-- Simple mode -->
<axiom-proof scoring="attempts"></axiom-proof>
<axiom-proof scoring="completion"></axiom-proof>
<axiom-proof scoring="none"></axiom-proof>

<!-- Full configuration -->
<axiom-proof scoring='{
  "mode": "attempts",
  "baseScore": 100,
  "attemptPenalty": 10,
  "maxPenalty": 50
}'></axiom-proof>

Scoring Parameters

| Parameter | Default | Description | |-----------|---------|-------------| | mode | "completion" | Scoring mode | | baseScore | 100 | Maximum score | | attemptPenalty | 5 | Points deducted per attempt (attempts mode) | | maxPenalty | 20 | Maximum total penalty (attempts mode) |

SCORM Integration

Enable SCORM for LMS integration:

<axiom-proof
  scorm
  problem-id="lesson1-problem1"
  mode="exercise"
  premises='["P"]'
  goal="P ∨ Q"
></axiom-proof>

SCORM Features

  • Auto-detects SCORM 1.2 or 2004 API
  • Saves/restores student progress via suspend_data
  • Reports scores on completion
  • Updates completion and success status
  • Falls back to localStorage when no LMS detected

Multiple Problems with SCORM

<axiom-proof scorm problem-id="q1" premises='["P"]' goal="P ∨ Q"></axiom-proof>
<axiom-proof scorm problem-id="q2" premises='["P ∧ Q"]' goal="Q"></axiom-proof>
<axiom-proof scorm problem-id="q3" premises='["P → Q", "P"]' goal="Q"></axiom-proof>

Symbol Input

Toolbar

Enable the symbol toolbar with show-toolbar:

<axiom-proof show-toolbar></axiom-proof>

Keyboard Auto-Conversion

Type ASCII sequences that auto-convert to Unicode symbols:

| Type | Get | |------|-----| | -> | → | | /\ | ∧ | | \/ | ∨ | | <-> | ↔ | | ~ | ¬ | | _\|_ | ⊥ | | @ | ∀ | | $ | ∃ |

Rule Autocomplete

Type : after a formula to trigger rule autocomplete. Navigate with arrow keys, select with Enter/Tab.

Proof Format

Each line follows the format:

<formula> :<rule> <citations>

Subproofs use leading pipe characters for indentation:

P → Q           :PR
P               :PR
| P             :Hyp
| Q             :→E 1 2
P → Q           :→I 3-4

Citation Formats

  • Single line: 1, 5
  • Multiple lines: 1 3, 2 5
  • Line range (for subproofs): 3-7

Examples

Basic Modus Ponens

<axiom-proof
  mode="exercise"
  premises='["P", "P → Q"]'
  goal="Q"
  rules="propositional"
  show-toolbar
></axiom-proof>

Sandbox Mode

<axiom-proof
  mode="sandbox"
  rules="fol"
  show-toolbar
></axiom-proof>

Dark Theme with Custom Scoring

<axiom-proof
  mode="exercise"
  premises='["P ∧ Q"]'
  goal="Q ∧ P"
  theme="dark"
  scoring='{"mode": "attempts", "attemptPenalty": 5}'
></axiom-proof>

Custom Styled

<style>
  .my-proof {
    --axiom-syn-op: #e91e63;
    --axiom-syn-rule: #4caf50;
    --axiom-font-size: 16px;
    --axiom-radius: 0;
  }
</style>

<axiom-proof class="my-proof" mode="sandbox"></axiom-proof>

With Event Handling

<axiom-proof id="proof1" mode="exercise" premises='["P"]' goal="P ∨ Q"></axiom-proof>
<div id="status"></div>

<script>
  const proof = document.getElementById('proof1');
  const status = document.getElementById('status');

  proof.addEventListener('axiom:validate', (e) => {
    if (e.detail.isValid) {
      status.textContent = 'Valid so far...';
    } else {
      status.textContent = e.detail.issues[0] || 'Keep working...';
    }
  });

  proof.addEventListener('axiom:complete', (e) => {
    status.textContent = `Complete! Score: ${e.detail.score}`;
  });
</script>

TypeScript Support

Full TypeScript support with exported types:

import type {
  ProofState,
  ValidationResult,
  SuspendData,
  ProofRule,
  ScoringConfig,
  Theme,
  Mode,
  CursorPosition,
  EditorOptions,
  AxiomEvents,
} from 'axiom-embed';

Browser Support

  • Chrome/Edge 88+
  • Firefox 78+
  • Safari 14+

Requires native Custom Elements v1 and Shadow DOM support.

License

MIT