Custom Result Card Code Examples | Yext Hitchhikers Platform

Box Shadow Around Card on Hover

Add the following to the answers.scss file:

.[[card name]] {
  transition: box-shadow 0.3s;

  &:hover {
    box-shadow: 0 0 11px rgba(33, 33, 33, 0.2);
  }
}

Box Shadow on Hover Example


Universal Search Results with Only Top and Bottom Borders

Add the following to the answers.scss file:

.HitchhikerResultsStandard-title {
  border-left: none;
  border-right: none;
  border-top: none;
  border-bottom: 1px solid #c5bbb1;
}

.HitchhikerResultsStandard-titleLabel {
  border-left: none;
  border-right: none;
  border-top: none;
  border-bottom: none;
}

.HitchhikerResultsStandard-Card {
  border-left: none;
  border-right: none;
  border-top: none;
  border-bottom: 1px solid #c5bbb1;
  padding-bottom: 30px;
  padding-top: 7px;
}

.HitchhikerResultsStandard-viewMore {
  border-left: none;
  border-right: none;
  border-bottom: none;
  border-top: none;
}

.yxt-Card {
  border-left: none;
  border-right: none;
  border-top: none;
  border-bottom: 1px solid #c5bbb1;
}

Search Results with Only Top and Bottom Borders Example


Prominent Image Unification

The object-fit properties help define the behavior of the image if it isn’t inherently the fixed dimensions.

Add the following to the answers.scss file:

.HitchhikerProductProminentImage-imgWrapper {
  justify-content: center;
  padding: 0.5rem;
}
.HitchhikerProductProminentImage-img {
  width: auto;
  height: 200px;
  object-fit: cover;
  object-position: center;
}

This will standardize your images to all have a height of 200px, with a .5rem border around your images.


Add Circular Headshots

The best way to accomplish this is to do so via the object-fit property. Learn more about this property here .

The below CSS would make the images circular, focusing on the center of the image, with a standard size.

Add the following to the answers.scss file:

.HitchhikerProductProminentImage-img {
  width: 100%;
  height: 200px;
  object-fit: cover;
  object-position: center;
  border-radius: 50%;
  padding: 1rem;
}

If you would like to differentiate certain properties for the Desktop and Mobile experiences you can use media queries :

.HitchhikerProductProminentImage-img {
  width: 100%;
  height: 200px;
  object-fit: cover;
  object-position: center;
  border-radius: 50%;
  padding: 1rem;

  @media only screen and (max-width: 768px) {
    border-radius: 0px;
    padding: 0px;
  }
}


Add Email to Cards

In order to do this the right way, we’ll need to do three things:

1. Update the custom card’s component.js to pass through the Email field, as well as the details needed to successfully fire an Analytics event.

email: profile.emails ? profile.emails[0] : null,
emailEventOptions: this.addDefaultEventOptions(),

The email attribute passes in the first email address of the Emails field (if populated), otherwise returns null. Modify this to align with the field that stores your email addresses.

The emailEventOptions attribute just passes the standard data (entity ID, universal vs. vertical search, etc.) that allows us to fire an Analytics event.

2. Update the custom card’s template.hbs to add a section for Email that correctly formats the link.

First, we’ll create an {{> email}} partial. Add the below within the template.hbs file, preferably below the {{#*inline 'phone'}} partial.

{{#*inline 'email'}} {{#if card.email}}
<div class="HitchhikerLocationStandard-email">
  <a
    href="mailto:{{card.email}}"
    data-eventtype="EMAIL"
    data-eventoptions="{{json card.emailEventOptions}}"
    target="_blank"
  >
    {{card.email}}
  </a>
</div>
{{/if}} {{/inline}}

Here, we’ve added a new class HitchhikerLocationStandard-email that allows us to style this email in the future. We’re then adding a link - to create an email link, simply add mailto: in front of the email address. We’re also passing data-eventtype and data-eventoptions so that an EMAIL event fires when a user clicks on the link. Lastly, the text of the link will be the email itself, enclosed within the <a> tag.

Now that we’ve defined the partial, we need to reference it so it’s placed on the card. If you’re forking from a location-standard or professional-standard card, you can navigate to the contactInfo partial in your file and add the email partial underneath the address & phone.

{{#* inline "contactInfo"}}
<div class="HitchhikerLocationStandard-contactInfo">
  {{#if (any card.phone card.address card.email)}}
  <div class="HitchhikerLocationStandard-core">
    {{> address }}
    {{> phone }}
    {{> email }}
  </div>
  {{/if}}

You’ll see that we both added the card.email in the #if statement, as well as added the partial below the address and phone partials.

3. Optional - add CSS to update the link styling

Add the following to the answers.scss file:

.HitchhikerLocationStandard-email {
  color: var(--yxt-color-brand-primary);
  margin-top: 0.5rem;

  a {
    text-decoration: underline;

    &:hover {
      text-decoration: none;
    }
  }
}


Display a List without Bullets

You will utilize list-style-type to display a list on cards and exclude the bullet points.

Add the following to the answers.scss file:

.HitchhikerMenuItemStandard-listItems {
  list-style-type: NONE;
}


Add a Conditional CTA Based on Boolean

You can easily do this by adding a boolean to your component.js file, and modifying the Handlebars template to only show the third CTA if that boolean evaluates to true.

1. Update component.js

The first step is to pass the information about whether a provider is accepting patients. Add an attribute in your component.js file to store whether or not a provider is accepting new patients. If you use the profile field, when this is marked as ‘Yes’, the boolean will evaluate to true; if it’s marked as false or unpopulated, it will evaluate to false.

acceptingNewPatients: profile.acceptingNewPatients,

You’ll also need to provide the data mapping for the third CTA.

CTA3: { // The tertiary call to action for the card
  label: 'Book Appointment',
  iconName: 'calendar',
  url: profile.c_primaryCTA.link,
  target: '_top',
  eventType: 'BOOK_APPOINTMENT',
  eventOptions: this.addDefaultEventOptions(),
  // ariaLabel: '',
}

2. Update template.hbs

The next step is to use this boolean to only show the tertiary CTA when acceptingNewPatients = true.

We can do this using an if statement in Handlebars. We’ll check if card.acceptingNewPatients evaluates to true; if so, we’ll add the tertiary CTA. You can see the modifications to the CTA partial below:

{{#*inline 'ctas'}}
{{#if (any (all card.CTA1 card.CTA1.url) (all card.CTA2 card.CTA2.url)(all card.CTA3 card.CTA3.url))}}
<div class="HitchhikerLocationStandard-ctasWrapper">
  {{> CTA card.CTA1 ctaName="primaryCTA" }}
  {{> CTA card.CTA2 ctaName="secondaryCTA" }}
  {{#if card.acceptingNewPatients}}
    {{> CTA card.CTA3 ctaname="tertiaryCTA"}}
  {{/if}}
</div>
{{/if}}
{{/inline}}

This will hide the CTA if acceptingNewPatients is not populated or false, and show it if it’s true.


Center CTAs Vertically

With the default Handlebars template, the CTAs container will align with the details container on a card. If you’d like to change the alignment, you’ll need to modify the card template slightly.

If you want to shift the CTAs up to align them vertically within the card, rather than align them with the details container, you can move the title and subtitle into the ‘info’ div.

1. Update template.hbs

<div class="HitchhikerProfessionalStandard {{cardName}}">
  {{> image }}
  <div class="HitchhikerProfessionalStandard-body">
    <div class="HitchhikerProfessionalStandard-contentWrapper">
      <div class="HitchhikerProfessionalStandard-info">
        {{> title }}
        {{> subtitle }}
        {{> details }}
        {{> list }}
        {{> phone }}
      </div>
      {{> ctas }}
    </div>
  </div>
</div>

2. Add Styling

You can ensure the contentWrapper div extends the whole height of the card, and use justify-content and align-self to align the CTAs to the middle of the card.

Add the following to the answers.scss file:

.HitchhikerProfessionalStandard-contentWrapper {
  height: 100%;
}

.HitchhikerProfessionalStandard-ctasWrapper {
  justify-content: center;
  align-self: center;
}


Fix CTAs to Bottom of Card

An easy way to make sure the CTAs are fixed to the bottom of the card is to use Flexbox to our advantage.

Assuming you’re using the product-prominentImage card (or a forked version of it), add the following to the answers.scss file:

.HitchhikerProductProminentImage {
  &-body {
    height: 100%;
    display: flex;
    flex-direction: column;
  }

  &-contentWrapper {
    height: 100%;
    justify-content: space-between;
  }
}

This accomplishes the following:

  • Specifies the .HitchhikerProductProminentImage-body class to use flex and take up the full height of the card available after the image
  • Allows the .HitchhikerProductProminentImage-contentWrapper class to take up the full height of the container, and specifies the spacing between the description & the CTAs to be space-between, forcing the CTAs to the end of the card.

If you don’t have a description, the following CSS may help you as it will set the top margin to fix the CTAs container to the bottom of the card.

.HitchhikerProductProminentImage-ctasWrapper {
  margin-top: auto;
}

Add Dynamic Text Highlighting to a Result Card

When a user inputs a query and Search finds that a certain field is highlighted, that information can be displayed on result cards with the isHighlighted utility function and highlightField formatter.

When you add these to a card, users will see text formatted as "[[field name]]" mentions [[highlighted text]]. For example, if a users searches “Pulitzer Prize” to see which authors have received that award, any authors that are returned will have text on their result card noting that the text in the query matches data in the awards field.

Dynamic Text Highlight on Result Card

This is to cover the cases where you have keyword search or document search on a field, but it’s not displayed on your card by default.

To do this:

  1. Create a custom card where you’d like the dynamic highlighting displayed. If you have a custom card already created that you want to add this to, then move to the next step.
  2. In the card’s component.js file, navigate to dataForRender object and add the following dynamicHighlights variable:

    //check if "c_awards" is highlighted
     var dynamicHighlights = [];
     if (HitchhikerJS.isHighlighted("c_awards", profile.d_highlightedFields)) {
    
       const fullHighlight = profile.d_highlightedFields["c_awards"];
       dynamicHighlights.push({
             "fieldName": "Awards",
             "value": Formatter.highlightField(fullHighlight.value, fullHighlight.matchedSubstrings)
         });
         
     }
    • If you want to check a different field for text matches to highlight, you will update the references to “c_awards” in the code block as well as update fieldName. For example, if you want to return text from the name field, you would use the following:

      //check if "c_awards" is highlighted
      var dynamicHighlights = [];
      if (HitchhikerJS.isHighlighted("name", profile.d_highlightedFields)) {
      
      const fullHighlight = profile.d_highlightedFields["name"];
      dynamicHighlights.push({
         "fieldName": "Name",
         "value": Formatter.highlightField(fullHighlight.value, fullHighlight.matchedSubstrings)
      });
           
      }
    • List fields also work very similarly. Instead, you’d need to iterate through the items before adding them to your dynamicHighlights array. Here’s an example of adding dynamic highlights with the “Languages” built-in field:

      if (profile.d_highlightedFields["languages"]) {
      const fullHighlight = profile.d_highlightedFields["languages"];
      for (const item in fullHighlight) {
      if (fullHighlight[item].matchedSubstrings && fullHighlight[item].matchedSubstrings.length > 0) {
      dynamicHighlights.push({
        "fieldName": "Languages",
        "value": Formatter.highlightField(fullHighlight[item].value, fullHighlight[item].matchedSubstrings)
      });
      }
      };
      }
  3. In the same component.js file, add a new property, highlightedField, to the return statement. Set it up to reference the new dynamicHighlights variable you just created:

    highlightedField: dynamicHighlights,

    When it is complete, the dataForRender in your component.js should look something like this:

    dataForRender(profile) {
       
    //check if "c_awards" is highlighted
     var dynamicHighlights = [];
     if (HitchhikerJS.isHighlighted("c_awards", profile.d_highlightedFields)) {
       const fullHighlight = profile.d_highlightedFields["c_awards"];
       dynamicHighlights.push({
             "fieldName": "Awards",
             "value": Formatter.highlightField(fullHighlight.value, fullHighlight.matchedSubstrings)
         });
         
     }
     
     return {
       title: profile.name, // The header text of the card
       url: profile.website || profile.landingPageUrl, 
       target: '_top', // If the title's URL should open in a new tab, etc.
       titleEventOptions: this.addDefaultEventOptions(),
       details: profile.description, // The text in the body of the card
       highlightedField: dynamicHighlights, //new
       showMoreDetails: {
         showMoreLimit: 750, // Character count limit
         showMoreText: 'Show more', // Label when toggle will show truncated text
         showLessText: 'Show less' // Label when toggle will hide truncated text
       }
     };
    }
  4. Now add the highlights to the card’s template.js file. Start by adding this code to the bottom of the file:

    {{#*inline 'highlightedField'}}
    {{#if card.highlightedField}}
    <ul class="highlights">
    {{#each card.highlightedField}}
      <li class="highlight">"{{this.fieldName}}" mentions {{{this.value}}}</li>
    {{/each}}
    </ul>
    {{/if}}
    {{/inline}}
    {{!-- other partials below --}}
  5. At the top of your file, there is a code block with the structure of the card. You will see things like title, subtitle, and details in the order that appear on the card. Add {{> highlightedField}} in the position you want it to display on the card. In most cases you will want to add it below the {{> details}} like so:

    <div class="HitchhikerStandard {{cardName}}">
    {{> image }}
    <div class="HitchhikerStandard-body">
      {{> title }}
      {{> subtitle }}
      <div class="HitchhikerStandard-contentWrapper">
        <div class="HitchhikerStandard-info">
          {{> details }}
          {{> highlightedField }}
        </div>
        {{> ctas }}
      </div>
    </div>
    </div>

Use Conditional Placeholder Images

One common issue we’ve seen is gaps in data, especially for images such as for headshots.

KG with people entities missing headshots

This example walks through using the JavaScript conditional operator to use placeholder images for any agents missing a headshot image, where the placeholder images differ by gender. In other words, the logic is as follows:

  • If the Headshot field is populated, use that image.
  • If the Headshot field is empty and the Gender field is set to Female, use the female placeholder image.
  • Otherwise, fall back to the generic placeholder image.

Learn more about null checks .

Add the following image property in the component.js file of the desired forked card:

return {
  title: profile.name, // The header text of the card
  subtitle: profile.c_area, // The sub-header text of the card
  image: profile.headshot ? Formatter.image(profile.headshot).url : (profile.gender === 'Female' ? 'https://a.mktgcdn.com/p-sandbox/y0iJHYFeCERihQg0qv7j88TBeEnpHq-v_BLyHiDXOb8/142x200.png' : 'https://a.mktgcdn.com/p-sandbox/6DD2AHZA1kbe8M9ZQGW_JOwXFyWNnRTMDJXIGxvQK-4/321x450.jpg'),
  ...
}

cards with conditional placeholder images based on gender