Kindhire Reviews Reviews Block - Let's templates
Unlock every premium block and template with All-Access from $20 / 3 months, cancel anytime. Get All-Access
All blocks
Free Reviews

Kindhire Reviews

A reviews block extracted from the Kindhire template, available in Tailwind CSS and Bootstrap.

0 1
Kindhire Reviews preview
kindhire.html
<!DOCTYPE html>
<html lang="en">
<head>
  <script>
    window.FontAwesomeConfig = { autoReplaceSvg: 'nest' };
  </script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Kindhire Reviews | Tailwind CSS UI Block</title>
  <meta name="description" content="A reviews block extracted from the Kindhire template, available in Tailwind CSS and Bootstrap.">
  <meta name="keywords" content="Kindhire, Tailwind CSS template, HTML website template, business template, inclusive hiring, accessible job board, disability employment, recruitment platform, remote jobs website, employer page">
  <meta name="author" content="Let's templates">
  <link rel="canonical" href="">

  <meta property="og:type" content="product">
  <meta property="og:site_name" content="Let's templates">
  <meta property="og:locale" content="en_US">
  <meta property="og:url" content="">
  <meta property="og:title" content="Kindhire | Tailwind CSS Business Template">
  <meta property="og:description" content="A warm, accessible Tailwind CSS business template for inclusive hiring platforms, with arch portraits, a story carousel and a live job matcher.">
  <meta property="og:image" content="">
  <meta property="og:image:alt" content="Kindhire template preview">

  <meta name="twitter:card" content="summary_large_image">
  <meta name="twitter:url" content="">
  <meta name="twitter:title" content="Kindhire | Tailwind CSS Business Template">
  <meta name="twitter:description" content="A warm, accessible Tailwind CSS business template for inclusive hiring platforms, with arch portraits, a story carousel and a live job matcher.">
  <meta name="twitter:image" content="">
  <meta name="twitter:image:alt" content="Kindhire template preview">
  <script src="https://cdn.tailwindcss.com/3.4.17"></script>
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,700;9..144,800&family=Urbanist:wght@400;500;600;700&display=swap" rel="stylesheet">
  <script>
    tailwind.config = {
      theme: {
        extend: {
          colors: { paper: '#f4f4f2', ink: '#141414', royal: { DEFAULT: '#0b4ea2', dark: '#083a7a', soft: '#dfe9f7' }, coral: { DEFAULT: '#f4846a', soft: '#fde3dc' }, leaf: { DEFAULT: '#3aa52a', soft: '#dff1db' }, sun: '#ffc928' },
          fontFamily: { sans: ['Urbanist', 'sans-serif'], serif: ['Fraunces', 'serif'] }
        }
      }
    }
  </script>
  <style>
    @view-transition { navigation: auto; }
    ::view-transition-old(root) { animation: 180ms ease-out both page-fade-out; }
    ::view-transition-new(root) { animation: 220ms ease-in both page-fade-in; }
    @keyframes page-fade-out { to { opacity: 0; } }
    @keyframes page-fade-in { from { opacity: 0; } }
    html { scroll-behavior: smooth; }
    [hidden] { display: none !important; }
    .wiggle { animation: wiggle 5s ease-in-out infinite; transform-origin: center; }
    @keyframes wiggle { 50% { rotate: 6deg; } }
    .logos { animation: logos 34s linear infinite; }
    .logos:hover { animation-play-state: paused; }
    @keyframes logos { to { transform: translateX(-50%); } }
    .ring-fill { transition: stroke-dashoffset .7s ease; }
    .timeline-fill { transform-origin: top; }
    .story { transition: opacity .4s ease; }
    .reveal-ready [data-reveal] { opacity: 0; transform: translateY(24px); transition: opacity .6s ease, transform .6s ease; transition-delay: var(--reveal-delay, 0ms); }
    .reveal-ready [data-reveal].is-visible { opacity: 1; transform: none; }
    @media (prefers-reduced-motion: reduce) {
      html { scroll-behavior: auto; }
      .wiggle, .logos { animation: none; }
      .ring-fill, .story { transition: none; }
      ::view-transition-old(root), ::view-transition-new(root) { animation: none; }
    }
  </style>
</head>
<body class="bg-paper font-sans text-ink antialiased">
<% active_page = "index" %>
<% nav = { "platform" => "Platform", "jobs" => "Jobs", "employers" => "For employers", "stories" => "Stories", "about" => "About", "contact" => "Contact" } %>
<% { "index" => "Home" }.merge(nav).each do |slug, label| %>
<% active = active_page == slug %>
<% end %>
<% arches = [["1507003211169-0a1dd7228f2d", "Smiling man in a white T-shirt", "Marco", "QA tester", "bg-royal", "halo", "lg:mt-0"], ["1580489944761-15a19d654956", "Smiling woman with her hair tied up", "Lina", "web designer", "bg-coral", "burst", "lg:mt-36"], ["1599566150163-29194dcaad36", "Young man with glasses and a beard", "Sam", "content moderator", "bg-leaf", "zigzag", "lg:mt-0"]] %>
<% arches.each_with_index do |(photo, alt, name, role, color, doodle, offset), index| %>
<% if index == 1 %>
<% end %>
<% if doodle == "halo" %>
<% elsif doodle == "burst" %>
<% else %>
<% end %>
<% unless index == 1 %>
<% else %>
<% end %>
<% end %>
<% stories = [["Elena Ruiz", "38, works from Lisbon, uses a wheelchair", "Moderating content is calm, focused work, and I can split my hours however my body needs. Two hours in the morning, two in the evening. My team checks in, never checks up.", "1573497019940-1c28c88b4f3e", "bg-royal"], ["Daniel Okafor", "29, works from Leeds, hard of hearing", "Every meeting has live captions and everything important is written down. For the first time I'm judged on my work, not on how fast I catch what people say.", "1500648767791-00dcc994a43e", "bg-coral"], ["Mei Tanaka", "34, works from Osaka, chronic fatigue", "Flexible deadlines changed everything. I design on good days and rest on hard ones, and nobody makes me explain myself.", "1573497019236-17f8177b81e8", "bg-leaf"]] %>
<!-- block:start reviews -->
  <section class="mx-auto max-w-7xl px-5 py-20 md:px-10" data-carousel>
    <div class="text-center">
      <h2 class="font-serif text-4xl font-bold tracking-tight md:text-6xl">Real careers, real people</h2>
      <p class="relative mx-auto mt-3 w-fit text-xl md:text-2xl">Yours could be next
        <svg class="absolute -bottom-3 left-0 h-3 w-full" viewBox="0 0 200 12" preserveAspectRatio="none" aria-hidden="true"><path d="M2 8c50-6 120-6 196-3M20 11c40-4 90-4 130-2" fill="none" stroke="#f4846a" stroke-width="2.5" stroke-linecap="round"/></svg>
      </p>
    </div>
    <div class="relative mt-16">
      <button type="button" data-carousel-prev class="absolute left-0 top-1/2 z-10 hidden h-12 w-12 -translate-y-1/2 place-items-center rounded-full bg-royal text-white transition hover:bg-royal-dark md:grid" aria-label="Previous story"><i class="fa-solid fa-arrow-left" aria-hidden="true"></i></button>
      <button type="button" data-carousel-next class="absolute right-0 top-1/2 z-10 hidden h-12 w-12 -translate-y-1/2 place-items-center rounded-full bg-royal text-white transition hover:bg-royal-dark md:grid" aria-label="Next story"><i class="fa-solid fa-arrow-right" aria-hidden="true"></i></button>
      <div class="mx-auto max-w-5xl md:px-20">
        <% stories.each_with_index do |(name, meta, quote, photo, color), index| %>
          <article data-slide <%= 'hidden' unless index.zero? %> class="story grid items-end gap-8 rounded-[3rem] bg-white p-6 pt-10 shadow-sm md:grid-cols-[16rem_1fr] md:gap-12 md:p-12" aria-roledescription="slide" aria-label="<%= index + 1 %> of <%= stories.size %>">
            <div class="relative mx-auto w-56 md:-mt-24 md:w-full">
              <span class="absolute inset-x-6 bottom-12 top-10 rounded-[1.5rem] <%= color %>" aria-hidden="true"></span>
              <img src="https://images.unsplash.com/photo-<%= photo %>?auto=format&fit=crop&w=500&h=600&q=80" alt="Portrait of <%= name %>" class="relative aspect-[5/6] w-full rounded-b-full object-cover">
              <svg class="wiggle absolute -left-4 bottom-10 h-16 w-16" viewBox="0 0 60 60" aria-hidden="true"><path d="M30 4l6 18 18-4-14 12 12 14-18-6-6 18-6-18-18 6 12-14L6 18l18 4Z" fill="none" stroke="#f4846a" stroke-width="3" stroke-linejoin="round"/></svg>
              <p class="mt-4 text-center font-bold"><%= name %></p>
            </div>
            <div>
              <p class="text-lg text-ink/70"><%= meta %></p>
              <blockquote class="relative mt-5 text-xl leading-relaxed md:text-2xl"><span class="absolute -left-10 top-0 hidden font-serif text-5xl text-royal md:block" aria-hidden="true">&laquo;</span><%= quote %><span class="font-serif text-3xl text-royal" aria-hidden="true"> &raquo;</span></blockquote>
              <a href="stories.html" class="mt-6 inline-flex items-center gap-2 font-semibold text-royal hover:underline">Read more stories <i class="fa-solid fa-arrow-right" aria-hidden="true"></i></a>
            </div>
          </article>
        <% end %>
      </div>
      <div class="mt-8 flex items-center justify-center gap-4">
        <button type="button" data-carousel-prev class="grid h-12 w-12 place-items-center rounded-full bg-royal text-white md:hidden" aria-label="Previous story"><i class="fa-solid fa-arrow-left" aria-hidden="true"></i></button>
        <div class="flex gap-2">
          <% stories.each_with_index do |story, index| %><button type="button" data-carousel-dot aria-label="Show story <%= index + 1 %>" aria-current="<%= index.zero? %>" class="h-3 w-3 rounded-full bg-ink/20 transition aria-[current=true]:w-8 aria-[current=true]:bg-royal"></button><% end %>
        </div>
        <button type="button" data-carousel-next class="grid h-12 w-12 place-items-center rounded-full bg-royal text-white md:hidden" aria-label="Next story"><i class="fa-solid fa-arrow-right" aria-hidden="true"></i></button>
      </div>
    </div>
  </section>
  <!-- block:end reviews -->
<script>
    const reducedMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;
    const watchMotion = !reducedMotion && !navigator.webdriver && 'IntersectionObserver' in window;

    document.querySelectorAll('[data-menu-toggle]').forEach((button) => {
      const menu = document.getElementById(button.getAttribute('aria-controls'));
      const close = menu.querySelector('[data-menu-close]');
      const set = (open) => { menu.hidden = !open; button.setAttribute('aria-expanded', String(open)); document.body.style.overflow = open ? 'hidden' : ''; (open ? close : button).focus(); };
      button.addEventListener('click', () => set(menu.hidden));
      close.addEventListener('click', () => set(false));
      document.addEventListener('keydown', (event) => { if (event.key === 'Escape' && !menu.hidden) set(false); });
    });

    document.querySelectorAll('[data-carousel]').forEach((root) => {
      const slides = [...root.querySelectorAll('[data-slide]')];
      const dots = [...root.querySelectorAll('[data-carousel-dot]')];
      let current = 0;
      const show = (index) => {
        current = (index + slides.length) % slides.length;
        slides.forEach((slide, i) => { slide.hidden = i !== current; });
        dots.forEach((dot, i) => dot.setAttribute('aria-current', String(i === current)));
      };
      root.querySelectorAll('[data-carousel-prev]').forEach((button) => button.addEventListener('click', () => show(current - 1)));
      root.querySelectorAll('[data-carousel-next]').forEach((button) => button.addEventListener('click', () => show(current + 1)));
      dots.forEach((dot, i) => dot.addEventListener('click', () => show(i)));
      root.addEventListener('keydown', (event) => { if (event.key === 'ArrowLeft') show(current - 1); if (event.key === 'ArrowRight') show(current + 1); });
    });

    document.querySelectorAll('[data-matcher]').forEach((root) => {
      const jobs = [...root.querySelectorAll('[data-job]')];
      const isOn = (el) => el.getAttribute('aria-pressed') === 'true' || el.selected === true || el.checked === true;
      const paint = () => {
        const skills = [...root.querySelectorAll('[data-skill]:not([data-job])')].filter(isOn).map((el) => el.dataset.skill);
        const needs = [...root.querySelectorAll('[data-need]')].filter(isOn).map((el) => el.dataset.need);
        let shown = 0;
        jobs.forEach((job) => {
          const match = (!skills.length || skills.includes(job.dataset.skill)) && needs.every((need) => job.dataset.needs.split(' ').includes(need));
          job.hidden = !match;
          if (match) shown += 1;
        });
        root.querySelector('[data-match-count]').textContent = shown;
        root.querySelector('[data-match-empty]').hidden = shown > 0;
      };
      root.querySelectorAll('[data-skill]:not([data-job])').forEach((chip) => chip.addEventListener('click', () => { if (chip.hasAttribute('aria-pressed')) chip.setAttribute('aria-pressed', String(chip.getAttribute('aria-pressed') !== 'true')); requestAnimationFrame(paint); }));
      root.addEventListener('change', paint);
      if (window.customElements) customElements.whenDefined('md-filter-chip').then(() => requestAnimationFrame(paint));
      paint();
    });

    document.querySelectorAll('[data-timeline]').forEach((root) => {
      const fill = root.querySelector('[data-timeline-fill]');
      if (!watchMotion) return;
      const update = () => {
        const box = root.getBoundingClientRect();
        fill.style.transform = `scaleY(${Math.min(1, Math.max(0, (innerHeight * 0.6 - box.top) / box.height))})`;
      };
      update();
      addEventListener('scroll', update, { passive: true });
    });

    document.querySelectorAll('[data-rings]').forEach((root) => {
      const rings = root.querySelectorAll('[data-ring]');
      if (!watchMotion) return;
      rings.forEach((ring) => { ring.style.strokeDashoffset = 326.73; });
      const observer = new IntersectionObserver((entries) => {
        if (!entries.some((entry) => entry.isIntersecting)) return;
        rings.forEach((ring) => { ring.style.strokeDashoffset = 326.73 * (1 - Number(ring.dataset.ring) / 100); });
        observer.disconnect();
      }, { threshold: 0.3 });
      observer.observe(root);
    });

    document.querySelectorAll('[data-chat]').forEach((root) => {
      const log = root.querySelector('[data-chat-log]');
      const add = (text, mine) => {
        const item = document.createElement('li');
        item.className = mine ? 'ml-auto max-w-[85%] rounded-3xl rounded-br-md bg-royal px-5 py-3 text-white' : 'max-w-[85%] rounded-3xl rounded-bl-md bg-paper px-5 py-3';
        item.textContent = text;
        log.append(item);
        log.scrollTop = log.scrollHeight;
      };
      root.querySelectorAll('[data-chat-question]').forEach((button) => button.addEventListener('click', () => {
        add(button.dataset.label || button.textContent.trim(), true);
        setTimeout(() => add(button.dataset.answer, false), reducedMotion ? 0 : 450);
      }));
    });

    document.querySelectorAll('[data-hover-list]').forEach((root) => {
      const image = root.querySelector('[data-hover-image]');
      root.querySelectorAll('[data-hover-row]').forEach((row) => {
        const show = () => { image.src = row.dataset.image; image.alt = row.dataset.alt; root.querySelectorAll('[data-hover-row]').forEach((other) => other.setAttribute('aria-current', String(other === row))); };
        row.addEventListener('mouseenter', show);
        row.addEventListener('focus', show);
        row.addEventListener('click', show);
      });
    });

    document.querySelectorAll('[data-job-board]').forEach((root) => {
      const list = root.querySelector('[data-board-list]');
      const items = [...list.children];
      const search = root.querySelector('[data-board-search]');
      const sort = root.querySelector('[data-board-sort]');
      const apply = () => {
        const query = String(search.value || '').trim().toLowerCase();
        const sorted = [...items].sort((a, b) => sort.value === 'salary' ? b.dataset.salary - a.dataset.salary : sort.value === 'hours' ? a.dataset.hours - b.dataset.hours : b.dataset.date - a.dataset.date);
        let shown = 0;
        sorted.forEach((item) => { const match = !query || item.textContent.toLowerCase().includes(query); item.hidden = !match; if (match) shown += 1; list.append(item); });
        root.querySelector('[data-board-count]').textContent = shown;
        root.querySelector('[data-board-empty]').hidden = shown > 0;
      };
      search.addEventListener('input', apply);
      sort.addEventListener('change', apply);
      const initial = new URLSearchParams(location.search).get('q');
      if (initial) search.value = initial;
      apply();
    });

    document.querySelectorAll('[data-calculator]').forEach((root) => {
      const money = (value) => `$${Math.round(value).toLocaleString('en-US')}`;
      const paint = () => {
        const hires = Number(root.querySelector('[data-hires]').value);
        const salary = Number(root.querySelector('[data-salary]').value);
        const fee = hires * salary * 0.08;
        const credit = hires * Math.min(salary * 0.25, 9600);
        root.querySelector('[data-hires-label]').textContent = hires;
        root.querySelector('[data-salary-label]').textContent = money(salary);
        root.querySelector('[data-fee]').textContent = money(fee);
        root.querySelector('[data-credit]').textContent = `−${money(credit)}`;
        root.querySelector('[data-net]').textContent = money(Math.max(0, fee - credit));
      };
      root.addEventListener('input', paint);
      paint();
    });

    document.querySelectorAll('[data-chat-form]').forEach((form) => {
      const log = form.querySelector('[data-chat-log]');
      const steps = [...form.querySelectorAll('[data-chat-step]')];
      const status = form.querySelector('[data-form-status]');
      let current = 0;
      const say = (text, mine) => {
        const bubble = document.createElement('li');
        bubble.className = mine ? 'ml-auto max-w-[85%] rounded-3xl rounded-br-md bg-royal px-5 py-3 text-white' : 'max-w-[85%] rounded-3xl rounded-bl-md bg-paper px-5 py-3';
        bubble.textContent = text;
        log.append(bubble);
        log.scrollTop = log.scrollHeight;
      };
      const show = (index) => steps.forEach((step, i) => { step.hidden = i !== index; });
      const field = (step) => step.querySelector('input, select, textarea, md-outlined-text-field, md-outlined-select');
      form.addEventListener('submit', (event) => {
        event.preventDefault();
        const input = field(steps[current]);
        if (!input.checkValidity() || !String(input.value || '').trim()) { status.textContent = form.dataset.error; input.focus(); return; }
        status.textContent = '';
        say(String(input.value), true);
        current += 1;
        if (current < steps.length) { say(steps[current].dataset.question, false); show(current); field(steps[current]).focus(); }
        else { say(form.dataset.success, false); show(-1); }
      });
      show(0);
    });

    document.querySelectorAll('[data-contact-form]').forEach((form) => {
      form.addEventListener('submit', (event) => {
        event.preventDefault();
        const status = form.querySelector('[data-form-status]');
        if (!form.checkValidity()) {
          status.textContent = form.dataset.error;
          form.querySelector(':invalid')?.focus();
          return;
        }
        status.textContent = form.dataset.success;
        form.reset();
      });
    });

    document.querySelectorAll('[data-current-year]').forEach((el) => { el.textContent = new Date().getFullYear(); });

    (() => {
      const items = document.querySelectorAll('[data-reveal]');
      if (!items.length || !watchMotion) return;
      document.documentElement.classList.add('reveal-ready');
      const observer = new IntersectionObserver((entries) => {
        entries.forEach((entry) => {
          if (!entry.isIntersecting) return;
          entry.target.classList.add('is-visible');
          observer.unobserve(entry.target);
        });
      }, { rootMargin: '0px 0px -10% 0px' });
      items.forEach((item) => observer.observe(item));
    })();
  </script>
</body>
</html>

From this template

Kindhire screenshot

Business

Kindhire

Unlimited access to all products

Unlock the entire library of premium templates and blocks. Renews automatically, cancel anytime.

$20 / 3 months or $60 / year
Get Unlimited Access