Drop-in spinner for any Angular 17+ button (no CSS imports): ngxsmk-button-spinner
Add a loading spinner to any existing
<button>with one attribute.
No component swap. No global stylesheet. SSR-safe. A11y-friendly.
✨ What it is
ngxsmk-button-spinner is a tiny Angular 17+ directive that overlays a spinner on your button during async work.
- ✅ Drop-in:
[ngxsmkButtonSpinner]="loading" -
✅ Two modes
- Inline (default): spinner appears after the text with a small gap
-
Overlay (centered):
[ngxsmkButtonSpinnerHideLabel]="true"
✅ Zero CSS imports — styles are injected once
✅ CSS variables theming (bind directly in the template)
✅ A11y:
role="status", configurablearia-label✅ SSR-safe
🧩 Install
npm i ngxsmk-button-spinner
Peer deps: @angular/core@>=17, @angular/common@>=17.
🚀 Quick start (Signals)
// app.component.ts
import { Component, signal } from '@angular/core';
import { NgxSmkButtonSpinnerDirective } from 'ngxsmk-button-spinner';
@Component({
selector: 'app-root',
standalone: true,
imports: [NgxSmkButtonSpinnerDirective],
template: `
<button [ngxsmkButtonSpinner]="saving()" (click)="save()">
Save
</button>
`
})
export class AppComponent {
saving = signal(false);
save() {
this.saving.set(true);
setTimeout(() => this.saving.set(false), 1200);
}
}
That’s it. No module boilerplate. No style imports.
🎛️ Theming (recommended): bind CSS variables directly
Bind these CSS variables right on the button for instant updates (no restarts, no object cloning):
-
--ngxsmk-color(spinner color) -
--ngxsmk-track(trail color, defaulttransparent) -
--ngxsmk-thickness(2px,3px, …) -
--ngxsmk-size(20px,1.2em, …) -
--ngxsmk-speed(700ms,450ms, …)
Inline (default)
<button
[ngxsmkButtonSpinner]="loading"
[style.--ngxsmk-color]="'#0ea5e9'"
[style.--ngxsmk-thickness]="'2px'"
[style.--ngxsmk-size]="'22px'"
[style.--ngxsmk-speed]="'600ms'">
Publish
</button>
Overlay (centered, text hidden)
<button
[ngxsmkButtonSpinner]="loading"
[ngxsmkButtonSpinnerHideLabel]="true"
[style.--ngxsmk-color]="'#0ea5e9'"
[style.--ngxsmk-thickness]="'6px'"
[style.--ngxsmk-size]="'20px'"
[style.--ngxsmk-speed]="'500ms'">
Save
</button>
Prefer variable binding over passing a theme object — it updates immediately with Angular HMR/Signals and avoids change-detection gotchas.
You can also set per-class defaults:
/* component.css */
.primary-btn {
--ngxsmk-color: #2563eb;
--ngxsmk-thickness: 3px;
--ngxsmk-size: 22px;
--ngxsmk-speed: 450ms;
}
🧠 API (tiny)
<button
[ngxsmkButtonSpinner]="loading" <!-- boolean or Signal<boolean> -->
[ngxsmkButtonSpinnerHideLabel]="true" <!-- optional overlay mode -->
[ngxsmkButtonSpinnerOptions]="{ ariaLabel: 'Saving' }">
Save
</button>
-
ngxsmkButtonSpinner: boolean | Signal<boolean)— toggles spinner; also disables the button. -
ngxsmkButtonSpinnerHideLabel?: boolean | Signal<boolean)— overlay mode when true. -
ngxsmkButtonSpinnerOptions?: { ariaLabel?: string }— a11y label for the spinner.
♿ Accessibility
- Spinner uses
role="status"with anaria-label(default: “Loading”). - Button is disabled while loading to prevent duplicate submissions.
- Overlay mode hides the label visually but keeps the status for screen readers.
🌐 SSR / hydration
The directive injects DOM only in the browser after view init. Toggle loading in response to user actions or browser-only effects.
🛠️ Troubleshooting
-
Theme not updating
Bind CSS variables directly (
[style.--ngxsmk-*]). This updates instantly without rebuilding. -
Spinner not centered in overlay
Ensure the button doesn’t clip positioned children with
overflow: hidden. - Text not hiding in overlay Upgrade to the latest build — the label is wrapped and hidden safely.
📦 Links
- GitHub: https://github.com/toozuuu/ngxsmk-button-spinner
- npm: https://www.npmjs.com/package/ngxsmk-button-spinner
If you build something with it, drop a comment — I’d love to see it!

Top comments (0)