Grid Style FAQ Component
A modern FAQ component with grid layout, category filtering, and interactive card design
Responsive Design
Yes
Dark Mode Support
No
lines
292
Browser Compatibility
No
Live Preview
Interact with the component without leaving the page.
Grid Style FAQ Component
A modern FAQ component featuring a responsive grid layout with interactive cards, category filtering, and smooth animations.
Features
- Grid Layout: Responsive masonry-style grid that adapts to different screen sizes
- Interactive Cards: Hover effects and smooth transitions for better user experience
- Category Filtering: Filter FAQ items by category with animated transitions
- Search Functionality: Real-time search across all FAQ content
- Expandable Cards: Click to expand/collapse FAQ answers with smooth animations
- Visual Indicators: Icons and badges for different categories and status
- Responsive Design: Works seamlessly on desktop, tablet, and mobile devices
- Accessibility: Full keyboard navigation and screen reader support
- Loading States: Skeleton loading for better perceived performance
- Infinite Scroll: Load more FAQ items as user scrolls (optional)
HTML Structure
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Grid Style FAQ Component</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="faq-grid-container">
<!-- Header -->
<div class="faq-header">
<h1>Frequently Asked Questions</h1>
<p>Find answers to common questions organized in an easy-to-browse grid layout</p>
</div>
<!-- Controls -->
<div class="faq-controls">
<!-- Search Bar -->
<div class="search-container">
<div class="search-input-wrapper">
<svg class="search-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.35-4.35"></path>
</svg>
<input type="text"
class="search-input"
placeholder="Search FAQ..."
aria-label="Search FAQ">
<button class="clear-search" style="display: none;" aria-label="Clear search">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
</div>
<!-- Category Filters -->
<div class="category-filters">
<button class="filter-btn active" data-category="all">
<span class="filter-icon">🌟</span>
<span>All</span>
<span class="count">12</span>
</button>
<button class="filter-btn" data-category="general">
<span class="filter-icon">📋</span>
<span>General</span>
<span class="count">4</span>
</button>
<button class="filter-btn" data-category="billing">
<span class="filter-icon">💳</span>
<span>Billing</span>
<span class="count">3</span>
</button>
<button class="filter-btn" data-category="technical">
<span class="filter-icon">⚙️</span>
<span>Technical</span>
<span class="count">3</span>
</button>
<button class="filter-btn" data-category="support">
<span class="filter-icon">🎧</span>
<span>Support</span>
<span class="count">2</span>
</button>
</div>
<!-- View Options -->
<div class="view-options">
<button class="view-btn active" data-view="grid" aria-label="Grid view">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="3" y="3" width="7" height="7"></rect>
<rect x="14" y="3" width="7" height="7"></rect>
<rect x="14" y="14" width="7" height="7"></rect>
<rect x="3" y="14" width="7" height="7"></rect>
</svg>
</button>
<button class="view-btn" data-view="list" aria-label="List view">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="8" y1="6" x2="21" y2="6"></line>
<line x1="8" y1="12" x2="21" y2="12"></line>
<line x1="8" y1="18" x2="21" y2="18"></line>
<line x1="3" y1="6" x2="3.01" y2="6"></line>
<line x1="3" y1="12" x2="3.01" y2="12"></line>
<line x1="3" y1="18" x2="3.01" y2="18"></line>
</svg>
</button>
</div>
</div>
<!-- Results Info -->
<div class="results-info">
<span class="results-count">Showing 12 of 12 questions</span>
<div class="sort-options">
<label for="sort-select">Sort by:</label>
<select id="sort-select" class="sort-select">
<option value="relevance">Relevance</option>
<option value="date">Date Updated</option>
<option value="popularity">Popularity</option>
<option value="alphabetical">Alphabetical</option>
</select>
</div>
</div>
<!-- FAQ Grid -->
<div class="faq-grid" data-view="grid">
<!-- FAQ Card 1 -->
<div class="faq-card" data-category="general" data-keywords="account registration create profile setup">
<div class="card-header">
<div class="category-badge general">
<span class="badge-icon">📋</span>
<span>General</span>
</div>
<div class="card-actions">
<button class="bookmark-btn" aria-label="Bookmark this question">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path>
</svg>
</button>
</div>
</div>
<div class="card-content">
<h3 class="question-title">How do I create an account?</h3>
<div class="question-preview">
Creating an account is simple and takes just a few minutes...
</div>
<div class="answer-content" style="display: none;">
<p>Creating an account is simple and takes just a few minutes. Follow these steps:</p>
<ol>
<li>Click the "Sign Up" button in the top right corner</li>
<li>Enter your email address and create a secure password</li>
<li>Verify your email address by clicking the link we send you</li>
<li>Complete your profile with basic information</li>
<li>Start using all our features immediately</li>
</ol>
<div class="helpful-section">
<span>Was this helpful?</span>
<div class="helpful-buttons">
<button class="helpful-btn" data-helpful="true">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"></path>
</svg>
Yes
</button>
<button class="helpful-btn" data-helpful="false">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"></path>
</svg>
No
</button>
</div>
</div>
</div>
</div>
<div class="card-footer">
<div class="card-meta">
<span class="last-updated">Updated Jan 15, 2024</span>
<span class="popularity">👍 24</span>
</div>
<button class="expand-btn" aria-expanded="false">
<span class="expand-text">Read More</span>
<svg class="expand-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="6,9 12,15 18,9"></polyline>
</svg>
</button>
</div>
</div>
<!-- FAQ Card 2 -->
<div class="faq-card" data-category="billing" data-keywords="payment methods credit card paypal billing">
<div class="card-header">
<div class="category-badge billing">
<span class="badge-icon">💳</span>
<span>Billing</span>
</div>
<div class="card-actions">
<button class="bookmark-btn" aria-label="Bookmark this question">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path>
</svg>
</button>
</div>
</div>
<div class="card-content">
<h3 class="question-title">What payment methods do you accept?</h3>
<div class="question-preview">
We accept all major payment methods to make your experience convenient...
</div>
<div class="answer-content" style="display: none;">
<p>We accept all major payment methods to make your experience convenient:</p>
<div class="payment-methods">
<div class="payment-category">
<h4>Credit & Debit Cards</h4>
<ul>
<li>Visa</li>
<li>MasterCard</li>
<li>American Express</li>
<li>Discover</li>
</ul>
</div>
<div class="payment-category">
<h4>Digital Wallets</h4>
<ul>
<li>PayPal</li>
<li>Apple Pay</li>
<li>Google Pay</li>
<li>Samsung Pay</li>
</ul>
</div>
</div>
<div class="helpful-section">
<span>Was this helpful?</span>
<div class="helpful-buttons">
<button class="helpful-btn" data-helpful="true">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"></path>
</svg>
Yes
</button>
<button class="helpful-btn" data-helpful="false">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"></path>
</svg>
No
</button>
</div>
</div>
</div>
</div>
<div class="card-footer">
<div class="card-meta">
<span class="last-updated">Updated Jan 10, 2024</span>
<span class="popularity">👍 18</span>
</div>
<button class="expand-btn" aria-expanded="false">
<span class="expand-text">Read More</span>
<svg class="expand-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="6,9 12,15 18,9"></polyline>
</svg>
</button>
</div>
</div>
<!-- FAQ Card 3 -->
<div class="faq-card" data-category="technical" data-keywords="password reset forgot login security">
<div class="card-header">
<div class="category-badge technical">
<span class="badge-icon">⚙️</span>
<span>Technical</span>
</div>
<div class="card-actions">
<button class="bookmark-btn" aria-label="Bookmark this question">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path>
</svg>
</button>
</div>
</div>
<div class="card-content">
<h3 class="question-title">How do I reset my password?</h3>
<div class="question-preview">
If you've forgotten your password, you can easily reset it...
</div>
<div class="answer-content" style="display: none;">
<p>If you've forgotten your password, you can easily reset it:</p>
<div class="reset-steps">
<div class="step">
<div class="step-number">1</div>
<div class="step-content">
<h4>Go to Login Page</h4>
<p>Click "Forgot Password" on the login screen</p>
</div>
</div>
<div class="step">
<div class="step-number">2</div>
<div class="step-content">
<h4>Enter Your Email</h4>
<p>Provide the email address associated with your account</p>
</div>
</div>
<div class="step">
<div class="step-number">3</div>
<div class="step-content">
<h4>Check Your Email</h4>
<p>Look for a password reset link in your inbox</p>
</div>
</div>
<div class="step">
<div class="step-number">4</div>
<div class="step-content">
<h4>Create New Password</h4>
<p>Follow the link and set a new secure password</p>
</div>
</div>
</div>
<div class="helpful-section">
<span>Was this helpful?</span>
<div class="helpful-buttons">
<button class="helpful-btn" data-helpful="true">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"></path>
</svg>
Yes
</button>
<button class="helpful-btn" data-helpful="false">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"></path>
</svg>
No
</button>
</div>
</div>
</div>
</div>
<div class="card-footer">
<div class="card-meta">
<span class="last-updated">Updated Jan 18, 2024</span>
<span class="popularity">👍 31</span>
</div>
<button class="expand-btn" aria-expanded="false">
<span class="expand-text">Read More</span>
<svg class="expand-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="6,9 12,15 18,9"></polyline>
</svg>
</button>
</div>
</div>
<!-- FAQ Card 4 -->
<div class="faq-card" data-category="support" data-keywords="contact support help customer service">
<div class="card-header">
<div class="category-badge support">
<span class="badge-icon">🎧</span>
<span>Support</span>
</div>
<div class="card-actions">
<button class="bookmark-btn" aria-label="Bookmark this question">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path>
</svg>
</button>
</div>
</div>
<div class="card-content">
<h3 class="question-title">How can I contact customer support?</h3>
<div class="question-preview">
We're here to help! Contact our support team through multiple channels...
</div>
<div class="answer-content" style="display: none;">
<p>We're here to help! Contact our support team through multiple channels:</p>
<div class="contact-options">
<div class="contact-option">
<div class="contact-icon">📧</div>
<div class="contact-details">
<h4>Email Support</h4>
<p>support@example.com</p>
<span>Response within 24 hours</span>
</div>
</div>
<div class="contact-option">
<div class="contact-icon">💬</div>
<div class="contact-details">
<h4>Live Chat</h4>
<p>Available on our website</p>
<span>Mon-Fri 9AM-6PM EST</span>
</div>
</div>
<div class="contact-option">
<div class="contact-icon">📞</div>
<div class="contact-details">
<h4>Phone Support</h4>
<p>1-800-123-4567</p>
<span>Mon-Fri 9AM-6PM EST</span>
</div>
</div>
</div>
<div class="helpful-section">
<span>Was this helpful?</span>
<div class="helpful-buttons">
<button class="helpful-btn" data-helpful="true">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"></path>
</svg>
Yes
</button>
<button class="helpful-btn" data-helpful="false">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"></path>
</svg>
No
</button>
</div>
</div>
</div>
</div>
<div class="card-footer">
<div class="card-meta">
<span class="last-updated">Updated Jan 12, 2024</span>
<span class="popularity">👍 15</span>
</div>
<button class="expand-btn" aria-expanded="false">
<span class="expand-text">Read More</span>
<svg class="expand-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="6,9 12,15 18,9"></polyline>
</svg>
</button>
</div>
</div>
</div>
<!-- No Results Message -->
<div class="no-results" style="display: none;">
<div class="no-results-icon">🔍</div>
<h3>No results found</h3>
<p>Try adjusting your search terms or browse all questions above.</p>
</div>
<!-- Load More Button -->
<div class="load-more-container" style="display: none;">
<button class="load-more-btn">
<span>Load More Questions</span>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="6,9 12,15 18,9"></polyline>
</svg>
</button>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
CSS Styles
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
line-height: 1.6;
color: #333;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.faq-grid-container {
max-width: 1200px;
margin: 0 auto;
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 40px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
}
/* Header Styles */
.faq-header {
text-align: center;
margin-bottom: 40px;
}
.faq-header h1 {
font-size: 2.5rem;
font-weight: 700;
background: linear-gradient(135deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 10px;
}
.faq-header p {
font-size: 1.1rem;
color: #666;
max-width: 600px;
margin: 0 auto;
}
/* Controls Styles */
.faq-controls {
display: flex;
flex-wrap: wrap;
gap: 20px;
align-items: center;
justify-content: space-between;
margin-bottom: 30px;
padding: 20px;
background: #f8f9fa;
border-radius: 15px;
border: 1px solid #e9ecef;
}
.search-container {
flex: 1;
min-width: 300px;
}
.search-input-wrapper {
position: relative;
display: flex;
align-items: center;
}
.search-input {
width: 100%;
padding: 12px 45px 12px 45px;
border: 2px solid #e9ecef;
border-radius: 25px;
font-size: 1rem;
transition: all 0.3s ease;
background: white;
}
.search-input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.search-icon {
position: absolute;
left: 15px;
color: #666;
z-index: 1;
}
.clear-search {
position: absolute;
right: 15px;
background: none;
border: none;
cursor: pointer;
color: #666;
padding: 5px;
border-radius: 50%;
transition: all 0.3s ease;
}
.clear-search:hover {
background: #f8f9fa;
color: #333;
}
/* Category Filters */
.category-filters {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.filter-btn {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 16px;
border: 2px solid #e9ecef;
border-radius: 25px;
background: white;
cursor: pointer;
transition: all 0.3s ease;
font-size: 0.9rem;
font-weight: 500;
}
.filter-btn:hover {
border-color: #667eea;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.2);
}
.filter-btn.active {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border-color: transparent;
}
.filter-btn .count {
background: rgba(0, 0, 0, 0.1);
padding: 2px 8px;
border-radius: 12px;
font-size: 0.8rem;
font-weight: 600;
}
.filter-btn.active .count {
background: rgba(255, 255, 255, 0.2);
}
/* View Options */
.view-options {
display: flex;
gap: 5px;
background: white;
border-radius: 10px;
padding: 5px;
border: 1px solid #e9ecef;
}
.view-btn {
padding: 8px 12px;
border: none;
background: transparent;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
color: #666;
}
.view-btn:hover {
background: #f8f9fa;
color: #333;
}
.view-btn.active {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
}
/* Results Info */
.results-info {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding: 15px 0;
border-bottom: 1px solid #e9ecef;
}
.results-count {
font-weight: 500;
color: #666;
}
.sort-options {
display: flex;
align-items: center;
gap: 10px;
}
.sort-select {
padding: 8px 12px;
border: 1px solid #e9ecef;
border-radius: 8px;
background: white;
cursor: pointer;
font-size: 0.9rem;
}
/* FAQ Grid */
.faq-grid {
display: grid;
gap: 20px;
margin-bottom: 30px;
}
.faq-grid[data-view="grid"] {
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
}
.faq-grid[data-view="list"] {
grid-template-columns: 1fr;
}
/* FAQ Card */
.faq-card {
background: white;
border-radius: 15px;
border: 1px solid #e9ecef;
overflow: hidden;
transition: all 0.3s ease;
position: relative;
}
.faq-card:hover {
transform: translateY(-5px);
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.1);
border-color: #667eea;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 20px 0;
}
.category-badge {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.category-badge.general {
background: linear-gradient(135deg, #4facfe, #00f2fe);
color: white;
}
.category-badge.billing {
background: linear-gradient(135deg, #43e97b, #38f9d7);
color: white;
}
.category-badge.technical {
background: linear-gradient(135deg, #fa709a, #fee140);
color: white;
}
.category-badge.support {
background: linear-gradient(135deg, #a8edea, #fed6e3);
color: #333;
}
.card-actions {
display: flex;
gap: 10px;
}
.bookmark-btn {
background: none;
border: none;
cursor: pointer;
padding: 8px;
border-radius: 50%;
transition: all 0.3s ease;
color: #666;
}
.bookmark-btn:hover {
background: #f8f9fa;
color: #667eea;
transform: scale(1.1);
}
.bookmark-btn.bookmarked {
color: #667eea;
background: rgba(102, 126, 234, 0.1);
}
.card-content {
padding: 20px;
}
.question-title {
font-size: 1.3rem;
font-weight: 600;
color: #333;
margin-bottom: 10px;
line-height: 1.4;
}
.question-preview {
color: #666;
font-size: 0.95rem;
line-height: 1.5;
margin-bottom: 15px;
}
.answer-content {
color: #555;
line-height: 1.6;
margin-top: 15px;
}
.answer-content h4 {
color: #333;
margin-bottom: 8px;
font-weight: 600;
}
.answer-content ol {
padding-left: 20px;
margin-bottom: 15px;
}
.answer-content li {
margin-bottom: 5px;
}
/* Payment Methods */
.payment-methods {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin: 15px 0;
}
.payment-category h4 {
color: #333;
margin-bottom: 10px;
font-size: 1rem;
}
.payment-category ul {
list-style: none;
padding: 0;
}
.payment-category li {
padding: 5px 0;
color: #666;
border-bottom: 1px solid #f0f0f0;
}
/* Reset Steps */
.reset-steps {
margin: 20px 0;
}
.step {
display: flex;
gap: 15px;
margin-bottom: 20px;
align-items: flex-start;
}
.step-number {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 0.9rem;
flex-shrink: 0;
}
.step-content h4 {
margin-bottom: 5px;
color: #333;
}
.step-content p {
color: #666;
font-size: 0.9rem;
}
/* Contact Options */
.contact-options {
display: grid;
gap: 15px;
margin: 15px 0;
}
.contact-option {
display: flex;
gap: 15px;
align-items: center;
padding: 15px;
background: #f8f9fa;
border-radius: 10px;
border: 1px solid #e9ecef;
}
.contact-icon {
font-size: 1.5rem;
width: 40px;
text-align: center;
}
.contact-details h4 {
margin-bottom: 5px;
color: #333;
}
.contact-details p {
color: #667eea;
font-weight: 600;
margin-bottom: 3px;
}
.contact-details span {
color: #666;
font-size: 0.9rem;
}
/* Helpful Section */
.helpful-section {
display: flex;
align-items: center;
gap: 15px;
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid #f0f0f0;
}
.helpful-buttons {
display: flex;
gap: 10px;
}
.helpful-btn {
display: flex;
align-items: center;
gap: 5px;
padding: 8px 12px;
border: 1px solid #e9ecef;
border-radius: 20px;
background: white;
cursor: pointer;
transition: all 0.3s ease;
font-size: 0.9rem;
}
.helpful-btn:hover {
border-color: #667eea;
color: #667eea;
}
.helpful-btn.voted {
background: #667eea;
color: white;
border-color: #667eea;
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
background: #f8f9fa;
border-top: 1px solid #e9ecef;
}
.card-meta {
display: flex;
gap: 15px;
align-items: center;
font-size: 0.9rem;
color: #666;
}
.popularity {
display: flex;
align-items: center;
gap: 5px;
}
.expand-btn {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 16px;
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border: none;
border-radius: 25px;
cursor: pointer;
transition: all 0.3s ease;
font-weight: 500;
}
.expand-btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3);
}
.expand-icon {
transition: transform 0.3s ease;
}
.expand-btn[aria-expanded="true"] .expand-icon {
transform: rotate(180deg);
}
.expand-btn[aria-expanded="true"] .expand-text::after {
content: " Less";
}
.expand-btn[aria-expanded="false"] .expand-text::after {
content: " More";
}
/* No Results */
.no-results {
text-align: center;
padding: 60px 20px;
color: #666;
}
.no-results-icon {
font-size: 4rem;
margin-bottom: 20px;
}
.no-results h3 {
font-size: 1.5rem;
margin-bottom: 10px;
color: #333;
}
/* Load More */
.load-more-container {
text-align: center;
margin-top: 30px;
}
.load-more-btn {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 15px 30px;
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border: none;
border-radius: 30px;
cursor: pointer;
transition: all 0.3s ease;
font-weight: 600;
font-size: 1rem;
}
.load-more-btn:hover {
transform: translateY(-3px);
box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3);
}
/* List View Styles */
.faq-grid[data-view="list"] .faq-card {
display: flex;
align-items: center;
padding: 20px;
}
.faq-grid[data-view="list"] .card-header {
padding: 0;
margin-right: 20px;
}
.faq-grid[data-view="list"] .card-content {
flex: 1;
padding: 0;
margin-right: 20px;
}
.faq-grid[data-view="list"] .card-footer {
padding: 0;
background: transparent;
border: none;
flex-shrink: 0;
}
.faq-grid[data-view="list"] .question-title {
font-size: 1.1rem;
margin-bottom: 5px;
}
.faq-grid[data-view="list"] .question-preview {
margin-bottom: 0;
font-size: 0.9rem;
}
/* Responsive Design */
@media (max-width: 768px) {
.faq-grid-container {
padding: 20px;
margin: 10px;
}
.faq-header h1 {
font-size: 2rem;
}
.faq-controls {
flex-direction: column;
align-items: stretch;
}
.search-container {
min-width: auto;
}
.category-filters {
justify-content: center;
}
.results-info {
flex-direction: column;
gap: 10px;
align-items: stretch;
}
.faq-grid[data-view="grid"] {
grid-template-columns: 1fr;
}
.faq-grid[data-view="list"] .faq-card {
flex-direction: column;
align-items: stretch;
}
.faq-grid[data-view="list"] .card-header,
.faq-grid[data-view="list"] .card-content {
margin-right: 0;
margin-bottom: 15px;
}
.payment-methods {
grid-template-columns: 1fr;
}
.contact-option {
flex-direction: column;
text-align: center;
}
}
@media (max-width: 480px) {
.filter-btn {
font-size: 0.8rem;
padding: 8px 12px;
}
.question-title {
font-size: 1.1rem;
}
.step {
flex-direction: column;
text-align: center;
}
.helpful-section {
flex-direction: column;
gap: 10px;
}
}
/* Animation Classes */
.fade-in {
animation: fadeIn 0.5s ease-in;
}
.slide-up {
animation: slideUp 0.3s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Loading States */
.loading {
opacity: 0.6;
pointer-events: none;
}
.skeleton {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
@keyframes loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
JavaScript Functionality
class GridFAQ {
constructor() {
this.searchInput = document.querySelector('.search-input');
this.clearSearchBtn = document.querySelector('.clear-search');
this.filterBtns = document.querySelectorAll('.filter-btn');
this.viewBtns = document.querySelectorAll('.view-btn');
this.sortSelect = document.querySelector('.sort-select');
this.faqGrid = document.querySelector('.faq-grid');
this.faqCards = document.querySelectorAll('.faq-card');
this.expandBtns = document.querySelectorAll('.expand-btn');
this.bookmarkBtns = document.querySelectorAll('.bookmark-btn');
this.helpfulBtns = document.querySelectorAll('.helpful-btn');
this.resultsCount = document.querySelector('.results-count');
this.noResults = document.querySelector('.no-results');
this.loadMoreBtn = document.querySelector('.load-more-btn');
this.currentFilter = 'all';
this.currentView = 'grid';
this.currentSort = 'relevance';
this.searchTerm = '';
this.visibleCards = [];
this.init();
}
init() {
this.bindEvents();
this.updateVisibleCards();
this.updateResultsCount();
}
bindEvents() {
// Search functionality
this.searchInput.addEventListener('input', this.debounce(this.handleSearch.bind(this), 300));
this.clearSearchBtn.addEventListener('click', this.clearSearch.bind(this));
// Filter functionality
this.filterBtns.forEach(btn => {
btn.addEventListener('click', this.handleFilter.bind(this));
});
// View toggle
this.viewBtns.forEach(btn => {
btn.addEventListener('click', this.handleViewChange.bind(this));
});
// Sort functionality
this.sortSelect.addEventListener('change', this.handleSort.bind(this));
// Expand/collapse cards
this.expandBtns.forEach(btn => {
btn.addEventListener('click', this.handleExpand.bind(this));
});
// Bookmark functionality
this.bookmarkBtns.forEach(btn => {
btn.addEventListener('click', this.handleBookmark.bind(this));
});
// Helpful votes
this.helpfulBtns.forEach(btn => {
btn.addEventListener('click', this.handleHelpfulVote.bind(this));
});
// Load more
if (this.loadMoreBtn) {
this.loadMoreBtn.addEventListener('click', this.handleLoadMore.bind(this));
}
// Keyboard navigation
document.addEventListener('keydown', this.handleKeyboard.bind(this));
}
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
handleSearch(e) {
this.searchTerm = e.target.value.toLowerCase().trim();
if (this.searchTerm) {
this.clearSearchBtn.style.display = 'block';
} else {
this.clearSearchBtn.style.display = 'none';
}
this.updateVisibleCards();
this.updateResultsCount();
}
clearSearch() {
this.searchInput.value = '';
this.searchTerm = '';
this.clearSearchBtn.style.display = 'none';
this.updateVisibleCards();
this.updateResultsCount();
}
handleFilter(e) {
const category = e.currentTarget.dataset.category;
// Update active filter button
this.filterBtns.forEach(btn => btn.classList.remove('active'));
e.currentTarget.classList.add('active');
this.currentFilter = category;
this.updateVisibleCards();
this.updateResultsCount();
}
handleViewChange(e) {
const view = e.currentTarget.dataset.view;
// Update active view button
this.viewBtns.forEach(btn => btn.classList.remove('active'));
e.currentTarget.classList.add('active');
this.currentView = view;
this.faqGrid.dataset.view = view;
// Add animation class
this.faqGrid.classList.add('fade-in');
setTimeout(() => {
this.faqGrid.classList.remove('fade-in');
}, 500);
}
handleSort(e) {
this.currentSort = e.target.value;
this.sortCards();
this.updateVisibleCards();
}
handleExpand(e) {
const card = e.currentTarget.closest('.faq-card');
const answerContent = card.querySelector('.answer-content');
const isExpanded = e.currentTarget.getAttribute('aria-expanded') === 'true';
if (isExpanded) {
answerContent.style.display = 'none';
e.currentTarget.setAttribute('aria-expanded', 'false');
} else {
answerContent.style.display = 'block';
answerContent.classList.add('slide-up');
e.currentTarget.setAttribute('aria-expanded', 'true');
// Scroll card into view
setTimeout(() => {
card.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}, 100);
}
// Track analytics
this.trackExpansion(card.querySelector('.question-title').textContent, !isExpanded);
}
handleBookmark(e) {
const btn = e.currentTarget;
const card = btn.closest('.faq-card');
const questionTitle = card.querySelector('.question-title').textContent;
btn.classList.toggle('bookmarked');
if (btn.classList.contains('bookmarked')) {
this.saveBookmark(questionTitle);
this.showToast('Question bookmarked!');
} else {
this.removeBookmark(questionTitle);
this.showToast('Bookmark removed');
}
}
handleHelpfulVote(e) {
const btn = e.currentTarget;
const isHelpful = btn.dataset.helpful === 'true';
const card = btn.closest('.faq-card');
const questionTitle = card.querySelector('.question-title').textContent;
const helpfulSection = btn.closest('.helpful-section');
// Remove previous votes
helpfulSection.querySelectorAll('.helpful-btn').forEach(b => {
b.classList.remove('voted');
});
// Add vote to clicked button
btn.classList.add('voted');
// Track vote
this.trackHelpfulVote(questionTitle, isHelpful);
this.showToast(isHelpful ? 'Thanks for your feedback!' : 'Thanks for letting us know');
}
handleLoadMore() {
// Simulate loading more content
this.loadMoreBtn.style.display = 'none';
this.showToast('All questions loaded');
}
handleKeyboard(e) {
// Escape key to close expanded cards
if (e.key === 'Escape') {
const expandedCards = document.querySelectorAll('.expand-btn[aria-expanded="true"]');
expandedCards.forEach(btn => {
btn.click();
});
}
// Ctrl/Cmd + F to focus search
if ((e.ctrlKey || e.metaKey) && e.key === 'f') {
e.preventDefault();
this.searchInput.focus();
}
}
updateVisibleCards() {
this.visibleCards = [];
this.faqCards.forEach(card => {
const category = card.dataset.category;
const keywords = card.dataset.keywords.toLowerCase();
const questionTitle = card.querySelector('.question-title').textContent.toLowerCase();
const questionPreview = card.querySelector('.question-preview').textContent.toLowerCase();
// Check category filter
const categoryMatch = this.currentFilter === 'all' || category === this.currentFilter;
// Check search term
const searchMatch = !this.searchTerm ||
keywords.includes(this.searchTerm) ||
questionTitle.includes(this.searchTerm) ||
questionPreview.includes(this.searchTerm);
if (categoryMatch && searchMatch) {
card.style.display = 'block';
card.classList.add('fade-in');
this.visibleCards.push(card);
// Highlight search terms
if (this.searchTerm) {
this.highlightSearchTerm(card, this.searchTerm);
} else {
this.removeHighlight(card);
}
} else {
card.style.display = 'none';
}
});
// Show/hide no results message
if (this.visibleCards.length === 0) {
this.noResults.style.display = 'block';
} else {
this.noResults.style.display = 'none';
}
}
updateResultsCount() {
const total = this.faqCards.length;
const visible = this.visibleCards.length;
this.resultsCount.textContent = `Showing ${visible} of ${total} questions`;
}
sortCards() {
const cardsArray = Array.from(this.faqCards);
cardsArray.sort((a, b) => {
switch (this.currentSort) {
case 'date':
const dateA = new Date(a.querySelector('.last-updated').textContent.replace('Updated ', ''));
const dateB = new Date(b.querySelector('.last-updated').textContent.replace('Updated ', ''));
return dateB - dateA;
case 'popularity':
const popularityA = parseInt(a.querySelector('.popularity').textContent.match(/\d+/)[0]);
const popularityB = parseInt(b.querySelector('.popularity').textContent.match(/\d+/)[0]);
return popularityB - popularityA;
case 'alphabetical':
const titleA = a.querySelector('.question-title').textContent;
const titleB = b.querySelector('.question-title').textContent;
return titleA.localeCompare(titleB);
default: // relevance
return 0;
}
});
// Re-append sorted cards
cardsArray.forEach(card => {
this.faqGrid.appendChild(card);
});
}
highlightSearchTerm(card, term) {
const elements = card.querySelectorAll('.question-title, .question-preview');
elements.forEach(element => {
const text = element.textContent;
const regex = new RegExp(`(${term})`, 'gi');
const highlightedText = text.replace(regex, '<mark>$1</mark>');
element.innerHTML = highlightedText;
});
}
removeHighlight(card) {
const elements = card.querySelectorAll('.question-title, .question-preview');
elements.forEach(element => {
const text = element.textContent;
element.innerHTML = text;
});
}
saveBookmark(questionTitle) {
let bookmarks = JSON.parse(localStorage.getItem('faq-bookmarks') || '[]');
if (!bookmarks.includes(questionTitle)) {
bookmarks.push(questionTitle);
localStorage.setItem('faq-bookmarks', JSON.stringify(bookmarks));
}
}
removeBookmark(questionTitle) {
let bookmarks = JSON.parse(localStorage.getItem('faq-bookmarks') || '[]');
bookmarks = bookmarks.filter(bookmark => bookmark !== questionTitle);
localStorage.setItem('faq-bookmarks', JSON.stringify(bookmarks));
}
loadBookmarks() {
const bookmarks = JSON.parse(localStorage.getItem('faq-bookmarks') || '[]');
this.faqCards.forEach(card => {
const questionTitle = card.querySelector('.question-title').textContent;
const bookmarkBtn = card.querySelector('.bookmark-btn');
if (bookmarks.includes(questionTitle)) {
bookmarkBtn.classList.add('bookmarked');
}
});
}
showToast(message) {
// Create toast element
const toast = document.createElement('div');
toast.className = 'toast';
toast.textContent = message;
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
padding: 12px 20px;
border-radius: 25px;
font-weight: 500;
z-index: 1000;
transform: translateX(100%);
transition: transform 0.3s ease;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
`;
document.body.appendChild(toast);
// Show toast
setTimeout(() => {
toast.style.transform = 'translateX(0)';
}, 100);
// Hide toast
setTimeout(() => {
toast.style.transform = 'translateX(100%)';
setTimeout(() => {
document.body.removeChild(toast);
}, 300);
}, 3000);
}
trackExpansion(questionTitle, expanded) {
// Analytics tracking for question expansion
console.log(`Question ${expanded ? 'expanded' : 'collapsed'}: ${questionTitle}`);
// In a real application, you would send this data to your analytics service
// analytics.track('faq_question_expanded', {
// question: questionTitle,
// expanded: expanded,
// timestamp: new Date().toISOString()
// });
}
trackHelpfulVote(questionTitle, isHelpful) {
// Analytics tracking for helpful votes
console.log(`Question voted ${isHelpful ? 'helpful' : 'not helpful'}: ${questionTitle}`);
// In a real application, you would send this data to your analytics service
// analytics.track('faq_helpful_vote', {
// question: questionTitle,
// helpful: isHelpful,
// timestamp: new Date().toISOString()
// });
}
}
// Initialize the FAQ component when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
const faqGrid = new GridFAQ();
faqGrid.loadBookmarks();
});
Usage Instructions
- Basic Setup: Include the HTML, CSS, and JavaScript files in your project
- Search: Type in the search box to filter FAQ items in real-time
- Filter by Category: Click category buttons to show only specific types of questions
- Change View: Toggle between grid and list views using the view buttons
- Sort Results: Use the sort dropdown to organize questions by relevance, date, popularity, or alphabetically
- Expand Questions: Click “Read More” to see full answers
- Bookmark: Click the bookmark icon to save questions for later
- Vote: Use the helpful/not helpful buttons to rate answers
Customization Options
- Categories: Modify the
data-categoryattributes and filter buttons to match your content - Keywords: Add relevant keywords to
data-keywordsfor better search results - Styling: Customize colors, fonts, and animations in the CSS
- Analytics: Implement real tracking in the
trackExpansionandtrackHelpfulVotemethods - Load More: Add pagination or infinite scroll functionality
- Icons: Replace emoji icons with custom SVG icons or icon fonts
Browser Support
- Modern Browsers: Full functionality in Chrome, Firefox, Safari, Edge
- Responsive: Works on desktop, tablet, and mobile devices
- Accessibility: Full keyboard navigation and screen reader support
- Performance: Optimized for smooth animations and fast filtering
Performance Features
- Debounced Search: Prevents excessive filtering during typing
- Efficient DOM Updates: Minimizes reflows and repaints
- Local Storage: Persists bookmarks between sessions
- Smooth Animations: CSS transitions for better user experience
HTML
62
lines
CSS
176
lines
JavaScript
54
lines
<div class="faq-grid-container">
<div class="faq-header">
<h1>Frequently Asked Questions</h1>
<p>Find answers to common questions</p>
</div>
<div class="faq-controls">
<div class="search-box">
<input type="text" id="faqSearch" placeholder="Search FAQs...">
<i class="search-icon">🔍</i>
</div>
<div class="category-filters">
<button class="filter-btn active" data-category="all">All</button>
<button class="filter-btn" data-category="general">General</button>
<button class="filter-btn" data-category="technical">Technical</button>
<button class="filter-btn" data-category="billing">Billing</button>
</div>
</div>
<div class="faq-grid" id="faqGrid">
<div class="faq-card" data-category="general">
<div class="faq-question">
<h3>What is this service?</h3>
<span class="toggle-icon">+</span>
</div>
<div class="faq-answer">
<p>This is a comprehensive service that helps you manage your projects efficiently.</p>
</div>
</div>
<div class="faq-card" data-category="technical">
<div class="faq-question">
<h3>How do I integrate the API?</h3>
<span class="toggle-icon">+</span>
</div>
<div class="faq-answer">
<p>You can integrate our API by following the documentation provided in your dashboard.</p>
</div>
</div>
<div class="faq-card" data-category="billing">
<div class="faq-question">
<h3>How does billing work?</h3>
<span class="toggle-icon">+</span>
</div>
<div class="faq-answer">
<p>Billing is processed monthly based on your usage and selected plan.</p>
</div>
</div>
<div class="faq-card" data-category="general">
<div class="faq-question">
<h3>Is there customer support?</h3>
<span class="toggle-icon">+</span>
</div>
<div class="faq-answer">
<p>Yes, we provide 24/7 customer support through multiple channels.</p>
</div>
</div>
</div>
</div>