Build a WordPress Team Member Grid with Modal Details

Create a responsive team member grid with modal profile details in WPBits Block Studio using repeater fields, Twig, CSS, and a small script.

WPBits Team
7 min read
#gutenberg-blocks #wordpress-development #repeater-fields #team-section #blockstudio
WordPress team member grid with modal profile details

A team section is one of those website components that looks simple until you need to make it reusable. Editors need to add people, upload photos, reorder team members, and update bios without asking a developer to edit markup every time.

This tutorial shows how to build a Team Member Grid block in WPBits Block Studio. The block uses a repeater field for team members, renders a responsive grid on the frontend, and opens each member’s full profile in a modal.

The important part: you can build the block through the product workflow instead of setting up a full custom block development stack.

Team member grid block in WordPress Team member profile modal details

Why This Block Is Usually More Work Than It Looks

The conventional way to build this in WordPress often involves several moving parts: registering a custom block, creating editor controls, handling repeatable data, writing frontend markup, styling the grid, and adding JavaScript for the modal behavior.

For a large product feature, that level of setup can make sense. For a team section, it is usually too much overhead. You still want clean output and a good editor experience, but you do not want a simple content block to turn into a custom React and build-tool project.

WPBits Block Studio keeps the workflow focused. You define the block, add the controls, add the template, add the styles and script, then use the block in the WordPress editor.

In this tutorial, you will create:

  • A reusable Team Member Grid Gutenberg block
  • A repeater field for unlimited team members
  • Fields for name, role, photo, and biography
  • A responsive card grid
  • Modal details for each team member
  • An editor-friendly block that clients can update themselves

Requirements

Before you start, make sure you have:

  • WordPress 6.0 or newer
  • WPBits Block Studio installed and activated
  • Access to the WordPress admin

No separate Node, webpack, or React setup is required for this tutorial workflow.

For broader product setup and feature references, see the WPBits Block Studio documentation.

Step 1: Create a New Block

Open the WordPress admin and go to Block Builder. Create a new block and enter the basic block details.

Use these settings:

SettingValue
Block Nameteam-member-grid
Block TitleTeam Member Grid
DescriptionDisplay team members in a responsive grid with modal details.
Categorywidgets

Keep the block name lowercase and descriptive. It becomes the technical identifier for the block, so team-member-grid is easier to recognize later than a generic name like custom-section.

Step 2: Add the Repeater Field

Team member grid attributes

Open the Attributes section and add a new attribute for the team member list.

SettingValue
Attribute Nameteam_members
Control Typerepeater
LabelTeam Members

A repeater is the right control for this block because every team member uses the same data structure. Editors can add more people without duplicating blocks, editing HTML, or managing separate custom post entries.

Step 3: Add Team Member Fields

Team member grid repeater field attributes

Inside the team_members repeater, add the fields below.

FieldAttribute NameControl TypeLabelDefault Value
NamenametextNameJohn Doe
RoleroletextRoleTeam Member
PhotoimageimagePhotoEmpty
BiographybiotextareaBiographyEnter team member biography here...

These fields give editors exactly what they need and nothing extra. The block stays easy to understand when someone opens it six months later to update the team page.

Step 4: Add the Template

Open the Template tab and add the template below.

<div class="team-member-grid">
{% for member in attributes.team_members %}
<div class="team-member-card" data-member-id="{{ loop.index }}">
{% if member.image %}
<img src="{{ member.image.url }}" alt="{{ member.name }}" class="team-member-photo" />
{% endif %}
<h3 class="team-member-name">{{ member.name }}</h3>
<p class="team-member-role">{{ member.role }}</p>
<button class="team-member-more" data-modal-trigger="{{ loop.index }}">Learn More</button>
</div>
<div class="team-member-modal" id="modal-{{ loop.index }}" style="display: none;">
<div class="modal-overlay" data-modal-close="{{ loop.index }}"></div>
<div class="modal-content">
<button class="modal-close" data-modal-close="{{ loop.index }}">&times;</button>
{% if member.image %}
<img src="{{ member.image.url }}" alt="{{ member.name }}" class="modal-photo" />
{% endif %}
<h2>{{ member.name }}</h2>
<p class="modal-role">{{ member.role }}</p>
<div class="modal-bio">{{ member.bio }}</div>
</div>
</div>
{% endfor %}
</div>

The template has one job: turn the team_members repeater data into frontend HTML.

  • attributes.team_members is the repeater value from the block attributes.
  • The loop renders one card and one modal for each team member.
  • member.name, member.role, member.image, and member.bio output the fields you added inside the repeater.
  • loop.index gives each card and modal a matching number, so the Learn More button can target the correct modal.

Step 5: Add the Modal JavaScript

Open the JavaScript tab and add the modal script.

document.addEventListener('DOMContentLoaded', function () {
// Open modal
document.querySelectorAll('[data-modal-trigger]').forEach(function (button) {
button.addEventListener('click', function () {
const modalId = this.getAttribute('data-modal-trigger');
const modal = document.getElementById('modal-' + modalId);
if (modal) {
modal.style.display = 'block';
document.body.style.overflow = 'hidden';
}
});
});
// Close modal
document.querySelectorAll('[data-modal-close]').forEach(function (element) {
element.addEventListener('click', function () {
const modalId = this.getAttribute('data-modal-close');
const modal = document.getElementById('modal-' + modalId);
if (modal) {
modal.style.display = 'none';
document.body.style.overflow = '';
}
});
});
});

The script exists only to connect the buttons to the modals.

  • It waits until the page content is loaded.
  • It finds every element with data-modal-trigger.
  • When a visitor clicks Learn More, it reads the trigger value and opens the modal with the matching modal- ID.
  • It finds every element with data-modal-close so the close button and overlay can hide the same modal.
  • It pauses page scrolling while a modal is open, then restores scrolling when the modal closes.

Step 6: Add the Frontend CSS

Open the CSS tab and add the grid and modal styles.

.team-member-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 2rem;
padding: 2rem 0;
}
.team-member-card {
background: #fff;
border-radius: 8px;
padding: 1.5rem;
text-align: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: transform 0.2s;
}
.team-member-card:hover {
transform: translateY(-4px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.team-member-photo {
width: 150px;
height: 150px;
border-radius: 50%;
object-fit: cover;
margin-bottom: 1rem;
}
.team-member-name {
font-size: 1.25rem;
margin: 0.5rem 0;
color: #333;
}
.team-member-role {
color: #666;
font-size: 0.95rem;
margin-bottom: 1rem;
}
.team-member-more {
background: #0073aa;
color: #fff;
border: none;
padding: 0.5rem 1.5rem;
border-radius: 4px;
cursor: pointer;
font-size: 0.95rem;
}
.team-member-more:hover {
background: #005a87;
}
.team-member-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 9999;
}
.modal-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
}
.modal-content {
position: relative;
background: #fff;
max-width: 600px;
margin: 5% auto;
padding: 2rem;
border-radius: 8px;
z-index: 10000;
max-height: 80vh;
overflow-y: auto;
}
.modal-close {
position: absolute;
top: 1rem;
right: 1rem;
background: none;
border: none;
font-size: 2rem;
cursor: pointer;
color: #666;
line-height: 1;
}
.modal-close:hover {
color: #333;
}
.modal-photo {
width: 200px;
height: 200px;
border-radius: 50%;
object-fit: cover;
display: block;
margin: 0 auto 1.5rem;
}
.modal-role {
color: #0073aa;
font-weight: 600;
text-align: center;
margin-bottom: 1.5rem;
}
.modal-bio {
line-height: 1.6;
color: #333;
}

The CSS handles presentation only.

  • .team-member-grid creates the responsive grid.
  • .team-member-card, .team-member-photo, .team-member-name, and .team-member-role style the visible team cards.
  • .team-member-more styles the modal trigger button.
  • .team-member-modal, .modal-overlay, and .modal-content create the full-screen modal layer.
  • The modal photo, role, and bio styles keep the detail view readable when the biography is longer.

Step 7: Save and Use the Block

Save the block, then open any page or post in the WordPress block editor.

Insert Team Member Grid and add a few team members. For each item, fill in the name, role, photo, and biography. Reorder the items to confirm the repeater works as expected.

On the frontend, test the block before handing it to a client or publishing it on a production page.

Check that:

  • The grid responds cleanly on smaller screens
  • Each Learn More button opens the correct modal
  • The overlay and close button close the modal
  • Long biography text stays readable inside the modal
  • Photos are cropped consistently
  • The block still looks correct when one team member has a shorter role or bio

Why This Workflow Is Easier to Maintain

The value of this approach is not just the first build. It is the maintenance story.

With a conventional hand-coded block, a small content change can lead to developer involvement if the editor experience was not built carefully. With this WPBits Block Studio setup, editors manage team members directly in the block editor through structured controls.

You still get a custom block, but the repetitive setup work is reduced:

  • The repeater stores repeatable team member data
  • The template controls the frontend structure
  • The CSS controls the presentation
  • The small script handles the modal interaction
  • Editors manage the content without touching code

That is the right balance for a common website component: custom enough to fit the design, simple enough to maintain.

Practical Extensions

Once the base block is working, you can extend it without changing the core pattern.

Useful additions include:

  • A department field for larger teams
  • Social profile links
  • A location field for distributed teams
  • A featured toggle for leadership profiles
  • A select control for grid column options

The repeater pattern also works well for testimonials, speakers, service cards, portfolio items, and any other section where editors manage repeated content.

Final Result

You now have a reusable WordPress team member block with a responsive grid, modal profile details, and a clean editor workflow.

Instead of building the entire block stack manually, WPBits Block Studio lets you focus on the product tutorial essentials: define the fields, add the template, apply the CSS and JavaScript, then publish a block that editors can actually use.