In the BricksLabs Facebook group, a user asks:
I’m learning about ACF relationships and attempting to output a list of posts on any given page, and use the filter – select element in Bricks to refine the output by an ACF relationship field.
Can you recommend BL Tutorials that might help here?
—————————————————
CPT Downloads: Relationship with one or more Events.
CPT Events: Relationship with one or more Downloads.
Query loop: output all downloads and filter by event.
The built-in Bricks’ ‘Filter – Select’ element does not seem to work with a Relationship-type of field as of Bricks 1.10.3.
We can set up a custom select menu populated with the posts of a CPT and write some custom JavaScript for filtering the posts of another CPT.

This Pro tutorial shows how a custom select menu can be populated with the posts of a CPT that filter the posts of another CPT using JavaScript, where both CPT items are connected via an ACF Relationship field in Bricks.
Consider this example:
CPT 1: Events
Relationship field: Related Downloads
CPT 2: Downloads
Relationship field: Related Events
Events:

Downloads:

Event 1: Download 1, Download 3
Event 2: –
Event 3: Download 2
Event 4: Download 2, Download 3
After implementing the tutorial, a list of downloads that can be filtered by their related events on the downloads CPT archive page (could be a static Page as well):
While this article’s custom filter build process is specific to ACF Relationship, the overall process to build a select menu dropdown to filter one set of items with another set of related items is the same.
Step 1
Install and activate ACF.
Create your two custom post types.
Create a field group for each.
Event Fields:

Download Fields:

Enable bidirectional for both the Relationship fields.
Back in the Event Fields:

Back in the Download Fields:

Step 2
Create posts of both your post types.
Edit the posts (need to be done only for posts of 1 post type since you enabled bidirectional setting) and select the related posts of the other CPT.
Ex.:

Step 3
Edit the Page or the Template where you’d like to show the posts grid of the download CPT with Bricks.
In our dev site, we created a Template of the type ‘Archive’ and added a condition to apply it to the Downloads CPT archive.
json of the entire Section for this step can be copied from here (uses ACSS classes). Select the Code element, Sign code and save. If you are working on a static Page (vs the CPT archive template) select the query loop and set Post type to Downloads.

Manual instructions below.
Set up a query loop to output downloads.
Set the HTML tag of the query loop-enabled Block to li.
Set the HTML tag of the above li element’s parent to ul.
Add this class to the ul: downloads-grid
In our demo site where ACSS is active, we also added these classes:
list--nonegrid--3grid--l-2grid--m-1
For the li element add this class: download-card
(if your query loop-enabled element’s class is different, you should change download-card to that in the JS code below in Step 5)
In our demo site where ACSS is active, we also added focus-parent class.
Select the li element with download-card class active and set
Position: relative
Transition: translate 0.3s ease-in-out
CSS:
%root%:hover,
%root%:focus-visible {
translate: 0 -0.25em;
}
Add a new data attribute having:
Name: data-events
Value:
{echo:bl_get_escaped_related_event_ids}

We are going to define this function in the next step. This function will return the IDs of any/all related events for the current download post in the loop as a comma-separated string. We shall later extract the IDs from this string in JavaScript for filtering functionality.
Add Image element and h3 Heading elements inside the li element and set them to output the featured image and post title link.
Add this class to the h3 heading: clickable-parent
Add a Code element where you’d like the select dropdown to appear. Ex.: above the ul.
PHP & HTML:
<?php
$args = [
'post_type' => 'event',
'posts_per_page' => -1,
'orderby' => 'title',
'order' => 'ASC',
];
$events = get_posts( $args );
?>
<div class="event-filter">
<label for="event-select" id="event-select-label">Filter downloads by event:</label>
<select id="event-select" aria-labelledby="event-select-label" aria-controls="downloads-grid">
<option value="">All Events</option>
<?php foreach ( $events as $event ) : ?>
<option value="<?php echo esc_attr( $event->ID ); ?>"><?php echo esc_html( $event->post_title ); ?></option>
<?php endforeach; ?>
</select>
</div>
You could set a max width for this Code element to something like 400 if you do not want it to take up the full container width.
Step 4
Add the following in child theme‘s functions.php (w/o the opening PHP tag) or a code snippets plugin:
<?php
function bl_get_escaped_related_event_ids( int $post_id = 0 ): string {
if ( $post_id === 0 ) {
$post_id = get_the_ID();
}
$related_events = get_field( 'related_events', $post_id );
$event_ids = [];
if ( $related_events ) {
foreach ( $related_events as $event ) {
$event_ids[] = $event->ID;
}
}
return implode( ',', array_map( 'absint', $event_ids ) );
}
Whitelist the bl_get_escaped_related_event_ids function.
Ex.:
<?php
add_filter( 'bricks/code/echo_function_names', function() {
return [
'bl_get_escaped_related_event_ids'
];
} );
You should also add other functions (native or custom) being used in your Bricks instance besides bl_get_escaped_related_event_ids. This can be checked at Bricks → Settings → Custom code by clicking the Code review button.
More info on whitelisting can be found here.
Step 5
Back in Bricks, click on Settings (the gear icon) → PAGE SETTINGS → CUSTOM CODE.
Custom CSS:
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
Body (footer) scripts:
<script>
document.addEventListener('DOMContentLoaded', function() {
const eventSelect = document.getElementById('event-select');
const downloadItems = document.querySelectorAll('.download-card');
const downloadsGrid = document.getElementById('downloads-grid');
eventSelect.addEventListener('change', function() {
const selectedEventId = this.value;
let visibleCount = 0;
downloadItems.forEach(function(item) {
const itemEvents = item.dataset.events.split(',');
if (selectedEventId === '' || itemEvents.includes(selectedEventId)) {
item.style.display = 'flex';
item.removeAttribute('aria-hidden');
visibleCount++;
} else {
item.style.display = 'none';
item.setAttribute('aria-hidden', 'true');
}
});
// Update the live region with the result
const resultText = selectedEventId === ''
? `Showing all ${visibleCount} downloads.`
: `Showing ${visibleCount} download${visibleCount !== 1 ? 's' : ''} for the selected event.`;
downloadsGrid.setAttribute('aria-label', resultText);
// Optionally, you can also announce the result
announceResult(resultText);
});
function announceResult(text) {
let announcer = document.getElementById('filter-announcer');
if (!announcer) {
announcer = document.createElement('div');
announcer.id = 'filter-announcer';
announcer.setAttribute('aria-live', 'polite');
announcer.setAttribute('aria-atomic', 'true');
announcer.classList.add('visually-hidden');
document.body.appendChild(announcer);
}
announcer.textContent = text;
}
});
</script>
That’s it!
Test on the front end.