If you've seen Gojiberry, you know why it stands out. Their CRM tool doesn't just organize leads—it makes data management feel delightful. The interface is polished, modern, and thoughtfully designed. At the heart of it? A beautifully customized table that displays contacts with rich visual elements: circular avatars with gradients, LinkedIn badges, interactive email enrichment buttons, custom pagination, and so much more.
When most developers see a design like this, they assume they'll need to build everything from scratch or hack together multiple libraries. But what if you didn't have to?
We took on a challenge: replicate Gojiberry's table UI exactly using Simple Table. No workarounds, no "close enough"—pixel-perfect recreation. The result? A complete, production-ready implementation that showcases just how flexible Simple Table truly is.
Try it yourself: Check out our live CRM example to see the Gojiberry-inspired table in action, complete with all interactive features.
What Makes Gojiberry's UI Special
Gojiberry's table isn't your typical boring data grid. It's packed with custom UI elements that make it feel like a premium application:
Rich Contact Cards
Each row displays a contact with a gradient avatar, name (clickable link), job title, company name, and LinkedIn integration badge—all in a compact, visually appealing layout.
Interactive Elements
Email enrichment buttons that trigger loading states, "Fit" toggle buttons with three states, clickable list tags, and "Contact Now" CTAs—all inline with the data.
AI Scoring System
A creative visual representation of AI scores using fire emojis (🔥) that instantly communicates lead quality at a glance.
Custom Pagination
A fully custom footer with elegant page navigation, row counters, and a "rows per page" selector—all styled to match the brand perfectly.
These aren't just nice-to-haves—they're what make the interface feel alive and purposeful. And replicating them requires a table library with serious customization power.
The Challenge: Can Simple Table Handle This?
Most table libraries fall into one of two categories:
Category 1: Feature-Rich but Rigid
Some libraries provide tons of features but lock you into their design system. Want a custom cell with a gradient avatar and three lines of text? Good luck fighting with their cell renderers and CSS overrides. Custom pagination footer? You'll be deep in their API docs trying to piece together configuration flags.
Verdict: These libraries would require extensive workarounds and CSS hacks. The result? Code that's brittle and hard to maintain.
Category 2: Headless "Build Everything" Libraries
TanStack Table gives you complete control, but at what cost? You'd need to build the entire table structure, manage layouts, implement column resizing, handle virtualization, create the pagination logic, and more. For a UI like Gojiberry's, you're looking at hundreds of lines just to get the basics working before you even start customizing.
Verdict: While technically possible, you'd spend days building table infrastructure instead of focusing on your UI.
Enter Simple Table
Simple Table sits in the sweet spot: it provides all the essential table functionality out of the box (sorting, filtering, resizing, pagination, row selection), but exposes powerful customization points through render props. You get the structure and logic handled for you, but complete visual control where it matters.
How We Built It: Breaking Down the Implementation
Let's walk through the key parts of replicating Gojiberry's UI with Simple Table:
1. Rich Contact Cards with Avatars
The contact cell is the star of the show. It includes a gradient avatar, clickable name, LinkedIn badge, job title, and company. Here's how we did it with Simple Table's cellRenderer:
1{2 accessor: "name",3 label: "CONTACT",4 width: 290,5 cellRenderer: ({ row }) => {6 const initials = row.name7 .split(" ")8 .map((n) => n[0])9 .join("")10 .toUpperCase();1112 return (13 <div style={{ display: "flex", alignItems: "center", gap: "12px" }}>14 {/* Gradient Avatar */}15 <div16 style={{17 width: "40px",18 height: "40px",19 borderRadius: "50%",20 background: "linear-gradient(to right, #ff6b6b, #ee5a6f)",21 color: "white",22 display: "flex",23 alignItems: "center",24 justifyContent: "center",25 fontSize: "12px",26 fontWeight: "600",27 }}28 >29 {initials}30 </div>3132 {/* Contact Info */}33 <div style={{ display: "flex", flexDirection: "column", gap: "2px" }}>34 <div style={{ display: "flex", alignItems: "center", gap: "6px" }}>35 <span style={{ fontSize: "14px", fontWeight: "600", color: "#0077b5" }}>36 {row.name}37 </span>38 {row.linkedin && <LinkedInIcon />}39 </div>40 <div style={{ fontSize: "12px", color: "#64748b" }}>41 {row.title}42 </div>43 <div style={{ fontSize: "12px", color: "#64748b" }}>44 @ {row.company}45 </div>46 </div>47 </div>48 );49 },50}
Key insight: Simple Table's cellRenderer gives you complete control. It's just React—no special syntax, no limitations. Style it however you want.
2. Interactive Email Enrichment
The email column has three states: "Enrich" (clickable), "Enriching..." (loading), and the enriched email. This required state management within the cell renderer:
1const EmailEnrich = ({ rowId }) => {2 const [isLoading, setIsLoading] = useState(false);3 const [email, setEmail] = useState(null);45 const handleClick = () => {6 if (isLoading || email) return;78 setIsLoading(true);9 // Simulate API call10 setTimeout(() => {11 setEmail(generateRandomEmail());12 setIsLoading(false);13 }, 2000);14 };1516 if (email) {17 return <span className="email-badge">{email}</span>;18 }1920 if (isLoading) {21 return (22 <span className="email-badge">23 <Spinner />24 Enriching...25 </span>26 );27 }2829 return (30 <span onClick={handleClick} className="enrich-button">31 Enrich32 </span>33 );34};3536// In the column definition37{38 accessor: "emailStatus",39 label: "EMAIL",40 cellRenderer: ({ row }) => <EmailEnrich rowId={row.id} />41}
The power: Each cell can be a fully interactive React component with its own state, effects, and logic. Simple Table doesn't constrain you—it just provides the row data and gets out of your way.
3. Multi-State Toggle Buttons
The "Fit" column has three connected buttons (✓, ?, X) where clicking one toggles its active state:
1const FitButtons = ({ rowId }) => {2 const [selected, setSelected] = useState(null);34 return (5 <div style={{ display: "flex", alignItems: "center" }}>6 <button7 onClick={() => setSelected(selected === "fit" ? null : "fit")}8 style={{9 backgroundColor: selected === "fit" ? "#86efac" : "#e5e7eb",10 borderRadius: "6px 0 0 6px",11 // ... other styles12 }}13 >14 ✓15 </button>16 <button17 onClick={() => setSelected(selected === "partial" ? null : "partial")}18 style={{19 backgroundColor: selected === "partial" ? "#cbd5e1" : "#f3f4f6",20 // ... other styles21 }}22 >23 ?24 </button>25 <button26 onClick={() => setSelected(selected === "no" ? null : "no")}27 style={{28 backgroundColor: selected === "no" ? "#fca5a5" : "#fee2e2",29 borderRadius: "0 6px 6px 0",30 // ... other styles31 }}32 >33 X34 </button>35 </div>36 );37};
4. Custom Pagination Footer
The footer is where Simple Table's footerRenderer really shines. Gojiberry's pagination has page buttons, row counters, and a "rows per page" dropdown—all custom styled:
1<SimpleTable2 // ... other props3 footerRenderer={({4 currentPage,5 totalPages,6 startRow,7 endRow,8 totalRows,9 onPrevPage,10 onNextPage,11 onPageChange,12 hasPrevPage,13 hasNextPage,14 }) => (15 <div className="custom-footer">16 {/* Row info */}17 <p>18 Showing <span>{startRow}</span> to <span>{endRow}</span> of{" "}19 <span>{totalRows}</span> results20 </p>2122 {/* Controls */}23 <div className="footer-controls">24 {/* Rows per page selector */}25 <select onChange={handleRowsPerPageChange}>26 <option value="25">25</option>27 <option value="50">50</option>28 <option value="100">100</option>29 </select>3031 {/* Page buttons */}32 <nav className="pagination">33 <button onClick={onPrevPage} disabled={!hasPrevPage}>34 ‹35 </button>3637 {visiblePages.map(page => (38 <button39 key={page}40 onClick={() => onPageChange(page)}41 className={currentPage === page ? 'active' : ''}42 >43 {page}44 </button>45 ))}4647 <button onClick={onNextPage} disabled={!hasNextPage}>48 ›49 </button>50 </nav>51 </div>52 </div>53 )}54/>
The magic: Simple Table handles all the pagination logic—calculating pages, slicing data, managing state. You just render the UI. No rebuilding the wheel.
The Result: Pixel-Perfect Replication
The final implementation isn't just close—it's exactly what we wanted. Every detail from Gojiberry's design is there:
- Rich visual components: Gradient avatars, badges, icons—all rendering perfectly
- Full interactivity: Loading states, toggle buttons, clickable elements—everything works
- Custom pagination: Styled footer with all the controls we needed
- All table features intact: Column resizing, reordering, row selection, sorting—everything still works
- Dark mode support: The entire theme adapts beautifully to light and dark modes
See It Live
Don't take our word for it. Experience the Gojiberry-inspired table yourself. Resize columns, enrich emails, toggle buttons, navigate pages—it all works.
View Live CRM ExampleWhy This Matters for Your Projects
This isn't just about replicating one specific UI. It's about what it demonstrates:
Design Freedom
If Simple Table can replicate Gojiberry's intricate UI, it can handle your design system. Whether you're matching a Figma mockup, following Material Design, or building something completely custom—Simple Table won't hold you back.
Speed & Efficiency
We built this entire implementation in a fraction of the time it would take with other approaches. No fighting with CSS. No rebuilding core functionality. Just focused customization where it counts.
Maintainable Code
The code is clean, readable, and organized. Custom components are just React components. Styling is straightforward. No magic, no hacks—just good patterns that scale.
Production Ready
This isn't a proof of concept. It's a production-quality implementation with proper state management, error handling, accessibility considerations, and responsive design.
Build Your Own Custom Table
Ready to create your own beautifully customized table? Here's how to get started:
1. Install Simple Table
1npm install simple-table-core2# or3yarn add simple-table-core
2. Start with Custom Cell Renderers
The key to beautiful customization is the cellRenderer prop. Create a component that renders your cell exactly how you want:
1import { SimpleTable } from "simple-table-core";23const headers = [4 {5 accessor: "user",6 label: "User",7 cellRenderer: ({ row }) => (8 <div className="flex items-center gap-3">9 <img src={row.avatar} className="w-10 h-10 rounded-full" />10 <div>11 <div className="font-semibold">{row.name}</div>12 <div className="text-sm text-gray-500">{row.email}</div>13 </div>14 </div>15 ),16 },17 // ... more columns18];1920<SimpleTable21 defaultHeaders={headers}22 rows={data}23 rowIdAccessor="id"24/>
3. Add Custom Footer (Optional)
1<SimpleTable2 defaultHeaders={headers}3 rows={data}4 rowIdAccessor="id"5 shouldPaginate={true}6 footerRenderer={(props) => <MyCustomFooter {...props} />}7/>
Pro tip: Check out our complete CRM example source code to see how we built the Gojiberry-style table. Feel free to use it as a starting point for your own custom designs!
The Bottom Line
Gojiberry's table isn't just beautiful—it's functional, intuitive, and purpose-built for their use case. Replicating it wasn't about copying their design; it was about proving that Simple Table can handle any design.
Whether you're building a CRM, an analytics dashboard, an e-commerce admin panel, or anything in between—your table can look and feel exactly how you want it to. No compromises. No workarounds. Just powerful customization built on solid foundations.