Skip to main content

Install

npm install @sanity-labs/logo-soup
Requires solid-js 1.9 or later.

useLogoSoup

The Solid adapter wraps the core engine using from(), Solid’s built-in primitive for external store subscriptions. Options are passed as a getter function so Solid can track reactive dependencies inside it.
import { useLogoSoup } from "@sanity-labs/logo-soup/solid";
import { getVisualCenterTransform } from "@sanity-labs/logo-soup";
import { For, Show } from "solid-js";

function LogoStrip() {
  const result = useLogoSoup(() => ({
    logos: [
      { src: "/logos/acme.svg", alt: "Acme Corp" },
      { src: "/logos/globex.svg", alt: "Globex" },
      { src: "/logos/initech.svg", alt: "Initech" },
    ],
    baseSize: 48,
    scaleFactor: 0.5,
  }));

  return (
    <Show when={result.isReady}>
      <div style={{ "text-align": "center" }}>
        <For each={result.normalizedLogos}>
          {(logo) => (
            <img
              src={logo.croppedSrc || logo.src}
              alt={logo.alt}
              width={logo.normalizedWidth}
              height={logo.normalizedHeight}
              style={{
                display: "inline-block",
                margin: "0 14px",
                transform:
                  getVisualCenterTransform(logo, "visual-center-y") ?? "none",
              }}
            />
          )}
        </For>
      </div>
    </Show>
  );
}

Reactive Options

Options are passed as a getter function () => ProcessOptions. This is the standard Solid pattern for reactive props. Any signals read inside the getter are automatically tracked:
import { createSignal } from "solid-js";
import { useLogoSoup } from "@sanity-labs/logo-soup/solid";

function LogoStrip() {
  const [logos, setLogos] = createSignal(["/logos/acme.svg"]);
  const [baseSize, setBaseSize] = createSignal(48);

  const result = useLogoSoup(() => ({
    logos: logos(),
    baseSize: baseSize(),
    scaleFactor: 0.5,
  }));

  return (
    <div>
      <button onClick={() => setBaseSize((s) => (s === 48 ? 64 : 48))}>
        Toggle size
      </button>
      <button
        onClick={() => setLogos((prev) => [...prev, "/logos/globex.svg"])}
      >
        Add logo
      </button>
      <Show when={result.isReady}>
        <For each={result.normalizedLogos}>
          {(logo) => (
            <img
              src={logo.src}
              alt={logo.alt}
              width={logo.normalizedWidth}
              height={logo.normalizedHeight}
            />
          )}
        </For>
      </Show>
    </div>
  );
}
Internally, createEffect re-evaluates the getter whenever any signal it reads changes, then calls engine.process() with the new options.

Return Value

The return value is an object with reactive getters:
PropertyTypeDescription
isLoadingbooleantrue while images are being loaded and measured
isReadybooleantrue when all logos are normalized and ready to render
normalizedLogosNormalizedLogo[]The processed logos with computed dimensions
errorError | nullSet if all images fail to load
These are getters, not signals. You read them as properties (result.isReady), not function calls (result.isReady()). Each getter internally reads a signal created by from(), so they’re fully reactive in Solid’s tracking system.

Visual Center Alignment

Apply visual center alignment with the getVisualCenterTransform helper:
import { getVisualCenterTransform } from "@sanity-labs/logo-soup";

// Inside a component:
<For each={result.normalizedLogos}>
  {(logo) => (
    <img
      src={logo.src}
      alt={logo.alt}
      width={logo.normalizedWidth}
      height={logo.normalizedHeight}
      style={{
        transform:
          getVisualCenterTransform(logo, "visual-center-y") ?? "none",
      }}
    />
  )}
</For>;

Dark Mode

Pass backgroundColor so Logo Soup can detect contrast on opaque logos and apply irradiation compensation:
import { createSignal } from "solid-js";
import { useLogoSoup } from "@sanity-labs/logo-soup/solid";

function LogoStrip() {
  const [isDark, setIsDark] = createSignal(false);

  const result = useLogoSoup(() => ({
    logos: ["/logos/acme.svg", "/logos/globex.svg"],
    backgroundColor: isDark() ? "#1a1a1a" : "#ffffff",
  }));

  // ...
}

Cleanup

The engine is automatically destroyed when the owning reactive scope is disposed (via onCleanup). You don’t need to call destroy() manually in components. If you use useLogoSoup outside a component (e.g., in a createRoot), make sure to dispose the root when you’re done:
import { createRoot } from "solid-js";
import { useLogoSoup } from "@sanity-labs/logo-soup/solid";

const dispose = createRoot((dispose) => {
  const result = useLogoSoup(() => ({
    logos: ["/logo.svg"],
  }));

  // Use result...

  return dispose;
});

// Later: clean up
dispose();

How It Works Under the Hood

The Solid adapter uses three Solid primitives:
  • from() — Bridges the engine’s subscribe/getSnapshot interface into a Solid signal. from() accepts a producer function (set) => unsubscribe and returns a read-only accessor.
  • createEffect() — Re-runs when any signal read inside the options getter changes, calling engine.process() with the new values.
  • onCleanup() — Destroys the engine when the reactive scope is torn down.