Library / SDK
binimum/am-lyrics avatar
binimum/am-lyrics

am-lyrics: word-synced lyrics as a drop-in Web Component

Word synced lyrics in a Web Component

531 stars11 forksTypeScriptMPL-2.0

At a glance

What is it?
The am-lyrics package is an MPL-2.0 Web Component that fetches timed lyrics from several providers and animates them word by word in the style of Apple Music, with a Lit implementation, a React wrapper, a CDN build and a large set of CSS custom properties for theming. It suits somebody building a music player interface, and it depends on third party lyric APIs that it does not control.
Who is it for?
am-lyrics is worth using if you are building a music player and want the word-by-word lyric effect rather than a block of text, because the component is a plain Web Component with no framework requirement, because the provider chain degrades rather than fails, and because the theming is done with CSS custom properties rather than a style prop. It is the wrong tool if you need guaranteed lyrics, and the wrong tool if your application is offline.
Can I use it commercially?
Yes, with conditions. MPL-2.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What it does, and where the idea came from

The am-lyrics element is a web component for word-synced lyrics, and the project describes it as the author's take on Apple Music's word by word lyrics, using various APIs to fetch lyrics, with animations inspired by YouLy+, Apple Music-like Lyrics and the Swift reference in LyricsBlossom.

The design goal is stated as being faithful while also being lightweight and able to run on all major browsers and many mid-to-high end devices smoothly. That combination is the interesting constraint, because the effect it is imitating is an animated, per-word highlight that in the original runs as a native component, and a web implementation of it has to do the same work in JavaScript on every frame.

The implementation follows the open-wc recommendation, and that recommendation is what shapes the package. A Web Component is a browser standard, so the element works in a plain HTML page, in a React application, in a Vue application and in anything else that can load a script, without a framework adapter, and the element carries its own styling and behaviour in the shadow DOM rather than inheriting the host page's.

The demonstration is the component running fullscreen on Monochrome, a music player for the web, and the screenshot in the project documentation is that running fullscreen. The site is at lyrics.binimum.org, the package is published to npm as @uimaxbai/am-lyrics, and the licence is the Mozilla Public License 2.0, which is a file-level copyleft licence, a reasonable choice for a library that people embed in their own pages.

There are no GitHub releases, the last push to the repository was on 2026-10-22, and the package version in the manifest is 1.8.4.

Installing and using it, and the attribute list

There are two install routes, and the second is the one the documentation prefers.

bash
npm install @uimaxbai/am-lyrics # For react users and those crazy enough to not use the CDN

Or you use the CDN, which is the path for a plain page:

html
<script type="module">
  import 'https://cdn.jsdelivr.net/npm/@uimaxbai/am-lyrics/dist/src/am-lyrics.min.js';
</script>

<am-lyrics
  song-title="Uptown Funk"
  song-artist="Mark Ronson"
  song-album="Uptown Special"
  song-duration="269000"
  query="Uptown Funk Mark Ronson"
  current-time="0"
  duration=""
  highlight-color="#f00"
  font-family="'Inter', Arial, sans-serif"
  autoscroll
  interpolate
></am-lyrics>

The attributes are the API, and the list is worth reading because it shows the component is designed for a player rather than for a demo. There is a query string for searching, a music-id for a specific Apple Music song served through a backup Apple endpoint, a spotify-id for Spicy Lyrics which skips the Spotify search when supplied, an isrc for a direct lrc.red lookup and provider matching, and a ttml attribute that takes a string of TTML formatted lyrics to render directly and bypass all external requests.

Then the descriptive fields, song-title, song-artist and song-album, the album name of which is optional and used for provider matching, and song-duration in milliseconds, which is sent to LyricsPlus.

The playback fields are current-time, defaulting to zero, and duration, the playback timer duration in milliseconds, where the documentation says to set it to -1 to reset or stop playback. The presentation fields are highlight-color, defaulting to #fff, font-family, and the hide-source-footer flag, which is struck through in the table and therefore deprecated, with a default of false.

And two booleans that decide whether the component animates at all: autoscroll, on by default, which scrolls the container to keep the active line in view, and interpolate, also on by default, which enables the smooth word by word highlighting animation.

Where the lyrics come from, and the provider chain

The matching section describes a fallback chain, and the order matters more than any single provider.

lrc.red is the highest priority provider. An isrc requests the song JSON directly at a path built from the ISRC, with a three-second timeout. Otherwise the song title and song artist go to a match endpoint, including the optional album and the duration converted from milliseconds to seconds, and a standalone query uses a search endpoint. Match and search results supply the ISRC that is then used to fetch the song JSON. If a direct ISRC lookup fails, the component falls back to the supplied track and artist or the query.

BiniLyrics comes next, followed by Spicy Lyrics, and then the existing Unison, LyricsPlus, LRCLIB and Genius fallbacks, as metadata and synchronization quality allow. There is one special case worth knowing: if lrc.red returns a byModel flag set to true, BiniLyrics is skipped and Spicy Lyrics is preferred.

The architecture implied here is sensible for a component that cannot control its data sources. Rather than depending on one API, it has a primary source with a precise lookup path, a set of secondary sources with a stated preference, and a set of general-purpose fallbacks. The three-second timeout on the ISRC request is the detail that shows the design is aimed at real networks, since a hung request on a primary source would otherwise stall the component.

The ttml attribute is the escape hatch from all of this. Passing a string of TTML formatted lyrics renders it directly and bypasses all external requests, which means an application that already has the lyrics, or that wants a guaranteed result, or that has to work offline, can supply them and ignore the entire provider chain.

The licensing and the terms of those external services are a real consideration for anyone embedding this in a product, since a component that queries several third party lyric services on behalf of your users is making requests on their behalf, and the reliability of the feature is the reliability of services nobody in the project controls.

Theming, and the responsive presets that are built in

The appearance is controlled with CSS custom properties, and there are a lot of them, which tells you how much of the visual design lives in the component.

css
am-lyrics {
  /* Highlight color for active lyrics */
  --am-lyrics-highlight-color: #007aff;

  /* Alternative highlight color (fallback) */
  --highlight-color: #000;

  /* Swift-derived layout and motion defaults */
  --lyplus-font-size-base: 34px;
  --am-lyrics-line-height: 1.2;
  --am-lyrics-line-spacing: 25px;
  --am-lyrics-background-vocal-spacing: 15px;
  --am-lyrics-instrumental-height: 40px;
  --am-lyrics-inactive-scale: 0.98;
  --am-lyrics-highlight-radius: 16px;
  --am-lyrics-highlight-surface: rgba(255, 255, 255, 0.08);
  --am-lyrics-progression-feather: 30px;
  --am-lyrics-glow-radius: 5px;
  --am-lyrics-character-rise-peak: -1.25px;
  --am-lyrics-inline-padding: 20px;
}

The names carry their origin. The lyplus prefix marks the values taken from the YouLy+ reference, the background-vocal and instrumental groups are the layouts for the vocal sections and the instrumental breaks in a track, and the exit, enter and collapse durations are the animation timings.

There is a responsive behaviour underneath, and it is described precisely. The component automatically uses a 28px compact preset below 520 pixels and a 48px wide preset from 900 pixels. The compact and wide variables override those responsive values, and CSS variables take precedence over the properties in the table above.

That precedence rule is the one to remember, because there are two ways to set the same thing, the highlight-color attribute and the highlight custom property, and the custom property wins. A developer who sets the attribute and wonders why nothing happens has met that rule.

The scale of the variable list is a double-edged thing. It means a host application can reproduce the reference animation closely, and it also means that matching the original look means setting twenty variables rather than one.

The build, and the packaging details that matter

The package is a Lit component built with Rollup, and the scripts in the manifest describe the whole workflow.

The runtime dependencies are two, @babel/runtime and lit at ^3.1.4, so the component itself carries almost nothing. The development dependencies are where the open-wc and Storybook tooling lives, with the open-wc building-rollup, eslint-config and testing packages, the Lit React wrapper, and Storybook with the accessibility and essentials addons.

The scripts are short. The start script runs rollup in watch mode and a web dev server together, the build script clears dist and runs rollup with the config, and there is an analyze script that runs the custom elements manifest analyzer with the lit element plugin, with a prepublish that runs the same analysis while excluding dist. The lint and format scripts run ESLint over the TypeScript and HTML files with the gitignore honoured, and Prettier over the TypeScript.

Two things in that list are worth a note. The analyze script exists because a custom element is only useful if it can be documented, and the manifest analyser produces the metadata a registry needs, so the component can be published and documented automatically. And the build script prints Change the version in both files before it does anything, which is a warning from a maintainer who has been bitten by a version living in the manifest and in the package file at once.

The exports map carries three entry points, the package root, a standalone am-lyrics.js for direct script use, and a react entry for React applications, which is the whole reason the react export exists in a package that is a Web Component.

The repository is set up for browser testing rather than a Node test runner, with a web-test-runner config and a web-dev-server config, and a Storybook directory for the component gallery. There is also a husky directory for the commit hooks.

One rough edge in the tree is worth flagging: there are three lockfiles, a bun.lock, a bun.lockb and a yarn.lock alongside a package-lock.json. Four package managers' worth of resolution in one repository is a sign of a project that has been developed from more than one direction, and it is the kind of thing that produces an install that resolves differently depending on which tool runs it.

Editorial conclusion

am-lyrics is worth using if you are building a music player and want the word-by-word lyric effect rather than a block of text, because the component is a plain Web Component with no framework requirement, because the provider chain degrades rather than fails, and because the theming is done with CSS custom properties rather than a style prop. It is the wrong tool if you need guaranteed lyrics, and the wrong tool if your application is offline. Before you depend on it, check three things: that you understand where the lyrics come from, since the component queries lrc.red first and then a chain of fallbacks including BiniLyrics, Spicy Lyrics, Unison, LyricsPlus, LRCLIB and Genius, so availability and accuracy both depend on services you do not control, and that a direct TTML string can be passed to bypass all external requests if you need determinism; that the API surface matches what you need, because there are several identifiers, with an ISRC for a direct lookup, a Spotify track ID, an Apple Music song ID, a free text query and title, artist and album for matching, and each one takes a different path; and that you check the packaging, because the repository is published as am-lyrics while the npm package is scoped as @uimaxbai/am-lyrics, and the exports map carries three entry points including a React wrapper.

Frequently asked questions

How do I install and use the am-lyrics web component?

Run npm install @uimaxbai/am-lyrics, or import the minified build from the jsDelivr CDN with a module script. Then add an am-lyrics element with the attributes you need, such as song-title, song-artist, song-duration in milliseconds, a query, current-time, highlight-color and font-family, together with the autoscroll and interpolate booleans that enable automatic scrolling and word by word highlighting.

Where does am-lyrics get its lyrics from?

lrc.red is the highest priority provider, with a direct song JSON request for an ISRC under a three second timeout, a match lookup for title and artist including optional album and duration, and a search lookup for a free text query. BiniLyrics comes next, then Spicy Lyrics, then Unison, LyricsPlus, LRCLIB and Genius as fallbacks. A ttml attribute with the lyrics themselves bypasses all external requests.

How do I restyle the am-lyrics component?

With CSS custom properties set on the element, for example --am-lyrics-highlight-color, --lyplus-font-size-base, --am-lyrics-line-spacing, the background vocal and instrumental spacing and duration variables, and the scale and radius variables. The component applies a 28px compact preset below 520 pixels and a 48px wide preset from 900 pixels, and CSS variables take precedence over the element's own properties.

Official sources

  1. binimum/am-lyrics on GitHub
  2. Issues
  3. License: MPL-2.0
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/binimum-am-lyrics.svg)](https://hysenlabs.com/projects/binimum-am-lyrics)

Was this useful?