Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Bootstrap Star Rating Laravel Package

kartik-v/bootstrap-star-rating

A lightweight jQuery star rating plugin for Bootstrap with fractional star fills, RTL support, customizable sizes, colors, and themes. Includes clear/reset and readonly modes, keyboard accessibility, and a Yii2 widget wrapper for easy integration.

Deep Wiki
Context7

Getting Started

This is not a Laravel package — there is no service provider, no config file, and no Composer install. kartik-v/bootstrap-star-rating is a jQuery plugin (Krajee) that you wire into your Blade views. Treat it as a front-end asset, not a backend dependency.

Install the assets (pick one):

npm install bootstrap-star-rating
// resources/js/app.js
import $ from 'jquery';
window.$ = window.$ || $;
window.jQuery = window.jQuery || $;
import 'bootstrap-star-rating';
import 'bootstrap-star-rating/css/star-rating.css';
import 'bootstrap-star-rating/themes/krajee-fa/theme.css'; // requires Font Awesome

Or skip the build step entirely with the CDN:

@push('styles')
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/star-rating.min.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/themes/krajee-fa/theme.min.css">
@endpush

@push('scripts')
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/js/star-rating.min.js"></script>
@endpush

First use case — a product/review form with a fractional rating:

<form method="POST" action="{{ route('reviews.store', $product) }}">
  @csrf
  <input id="rating" name="rating" type="number" step="0.5" min="0" max="5"
         value="{{ old('rating', 0) }}" class="rating" required>
  @error('rating') <span class="text-danger">{{ $message }}</span> @enderror
  <button class="btn btn-primary">Submit</button>
</form>

@push('scripts')
<script>
  $(function () {
    $('#rating').rating({
      min: 0, max: 5, step: 0.5, stars: 5,
      showClear: true, showCaption: false,
      theme: 'krajee-fa'
    });
  });
</script>
@endpush

Where to look first: the Krajee docs for the options list (min, max, step, stars, displayOnly, showClear, showCaption, theme), then the theme CSS folders. The plugin replaces your <input> with a <div class="rating-container"> and keeps the original input in sync, so your form still submits normally.

Implementation Patterns

Reusable Blade component. Wrap the markup + init so you don't repeat it:

{{-- resources/views/components/star-rating.blade.php --}}
@props(['name', 'value' => 0, 'readonly' => false, 'step' => 0.5])

<input type="number" id="{{ $name }}" name="{{ $name }}"
       value="{{ old($name, $value) }}" step="{{ $step }}" min="0" max="5"
       class="rating" @if($readonly) disabled @endif>

@once
  @push('scripts')
  <script>
    document.addEventListener('DOMContentLoaded', () => {
      document.querySelectorAll('.rating').forEach(el => {
        const el$ = window.jQuery(el);
        el$.rating({
          step: parseFloat(el.step) || 0.5,
          stars: 5, min: 0, max: 5,
          showClear: !el.disabled,
          showCaption: false,
          displayOnly: el.disabled,
          theme: 'krajee-fa',
        });
      });
    });
  </script>
  @endpush
@endonce

Server-side validation is mandatory — the plugin gives you nothing on the backend:

$data = $request->validate([
    'rating' => ['required', 'numeric', 'min:0.5', 'max:5', function ($attr, $value, $fail) {
        if (fmod($value * 2, 1) !== 0.0) { // step 0.5
            $fail('Rating must be in half-star increments.');
        }
    }],
]);

$review = $product->reviews()->create([
    'rating' => $data['rating'],
    'user_id' => auth()->id(),
]);

Store as decimal(3,2) and cast to float in the model so averages render cleanly.

Read-only average display (product page, listing cards) — build once, no form:

<input type="number" value="{{ round($product->reviews_avg_rating, 1) }}"
       class="rating-display" step="0.1" min="0" max="5">

<script>
  $('.rating-display').rating({
    displayOnly: true, showClear: false, showCaption: true,
    starCaptions: v => v.toFixed(1) + ' / 5',
    theme: 'krajee-fa'
  });
</script>

Instant/AJAX save. Listen for the plugin's event, not the native change:

$('#rating').on('rating:change', function (event, value, caption) {
  if (!value) return; // clear button
  fetch('/api/ratings', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
      'Accept': 'application/json',
    },
    body: JSON.stringify({ product_id: productId, rating: value }),
  }).then(r => r.ok ? r.json() : Promise.reject(r));
});

Programmatic updates. Never $('#rating').val(4).trigger('change') — the plugin won't redraw. Use:

$('#rating').rating('update', 4.5);   // syncs stars + input
$('#rating').rating('reset');          // back to default
$('#rating').rating('refresh');        // re-read options/state

Livewire / Alpine. The plugin mutates the DOM, so morphing breaks it. Guard the container with wire:ignore, or re-init on livewire:navigated / Alpine x-init and call .rating('destroy') before re-rendering:

<div wire:ignore>
  <input type="number" wire:model="rating" class="rating" step="0.5" min="0" max="5">
</div>

Gotchas and Tips

  • Last release was 2021-09-20 — pin the version. Unpinned CDN/npm ranges can drift, and the plugin is jQuery-based. It is not Bootstrap 5 native; with BS5 you must still load jQuery and accept cosmetic quirks. Themes like krajee-gly assume Bootstrap 3 glyphicons and will render empty boxes on modern stacks — use krajee-fa (Font Awesome) or krajee-uni (self-contained SVG/font).
  • Script order is load-bearing. jQuery → Bootstrap → plugin → your init. Importing the plugin before window.jQuery is set is the #1 "rating is not a function" cause in Vite builds.
  • The <input> gets hidden and replaced. $('#rating').val() may read stale data mid-interaction. Always go through the API ('update', 'reset') and read the value on the server, never from the DOM.
  • Only rating:change is reliable. The plugin emits rating:change, rating:clear, rating:reset. Programmatic updates don't always fire native change. Bind to the plugin events.
  • Clear sets an empty value, not 0. If the field is required, showClear: true lets users submit nothing. Either drop showClear on required fields or validate required and render a sensible message. Don't cast '' to 0 accidentally — that "no rating" becomes a one-star review in averages.
  • step must divide max - min cleanly. step: 0.3 with max: 5 produces off-grid values and ugly captions. Use 0.5, 0.25, or 0.1. Fractional precision is the plugin's selling point — don't fight it with a nullable|integer rule.
  • stars vs max. max is the numeric ceiling; stars is how many icons are drawn. Setting stars: 10, max: 5 gives you 10 half-stars — intentional, but confusing if you didn't plan for it.
  • displayOnly: true ignores disabled/value changes unless you call 'refresh' or re-init. For listings that re-render via Turbo/Livewire, re-initialize explicitly.
  • Locale files are separate. Load js/locales/star-rating.<lang>.js after the plugin, then pass language: 'es'. It won't auto-detect from Laravel's app()->getLocale() — pass it through a view composer or @js.
  • Readonly star widgets are not accessible inputs. The visible stars are aria-light divs. Pair them with a visible numeric label for screen readers and keep the real numeric input (even hidden) for form semantics.
  • Server-side validation is the only validation. Any user can open devtools and post rating=99 or rating=-3. Enforce min/max/step on the Laravel side; the plugin is a UX layer only.
  • Vite + theme CSS font paths can break the icon fonts when you import the theme CSS through @vite. If stars render blank, either point the font url() at node_modules via resolve.alias, or fall back to the CDN for the theme stylesheet.
  • Old-value repopulation after a failed POST works because the plugin reads the input's value attribute on init — pass old('rating') in Blade, and it will render correctly on reload without extra JS.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle