How to Style and Brand Your Reservation Widget

Styling and branding your reservation widget is easy with eat app and just requires some quick configuration

To Style and Brand your widget, go to the Eat App Widget configuration section by:

- Clicking on the side menu at https://app.eatapp.co

- Select the online channels icon

- Click or tap the 'widget' option

 

Different ways to Customize your online booking widget: 

  1. Update your logo and background
  2. Add your own custom styles via CSS
  3. Add your own custom behaviour and styles with javascript
  4. Remove the time slots on your online waitlist (waitlist only for current time/walkins)
  5. Lock the continue button so it floats at the bottom of the screen when scrolling
  6. Enable a scrollable gallery for your events, preferences and tickets
  7. Add blacktext on highlighted cells (for supporting dark styles and backgrounds)
  8. Make your top logo 30% size
  9. Change the style of the widget for a specific event on a specific day
  10.  Change the wording on the confirmation page if there is a payment
  11.  Change the wording on the confirmation page if the party size is large
  12. Remove Venue name at the top of widget
  13. Make the waitlist button to always show on the widget
  14. Make the cover selector row sticky to the top
  15. Hide timeslots that are not available

Updating the Logo and Background

You can customize the logo and background by going to the look and feel section of the widget here: 

Screenshot 2024-01-13 at 08.16.26

Custom CSS Mode

You can customize all the CSS in your widget by toggling the CSS Style option in your 'widget look and feel settings' by choosing widget on the side menu here: 

Screenshot 2024-01-05 at 20.50.39

Then go to 'Look and feel' on your widget config of your choice and toggle on the customized CSS field. 

Screenshot 2024-01-05 at 20.47.52

 

You can then add any custom css you want to customize your widget! 

Copy and paste this example below to see how far your customization can go:

body {
  font-family: arial;
  background-color: #f5f5f5;
  corner-radius: 4px;
}

.text-primary {
  color: #dd8e00 !important;
  font-family: arial;
}

.timeslot {
  border-radius: 4px !important;
}

.search-cover-selector {
}

.search-date-selector {
}

.search-preference-selector {
}

.search-container {
  background-color: #f5f5f5;
}

.search-cover-selector,
.search-date-selector,
.search-preference-selector {
  background-color: #FFFFFF !important;
  border-color: transparent !important;
}

/* .timeslots - unchecked */
.timeslots .slot {
  font-size: 0.875rem;
  color: #111111 !important;
  border: 1px solid #320606;
  border-radius: 4px;
  cursor: pointer;
  font-weight: 100;
  line-height: 1.4;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

/* .timeslots - checked */
.timeslots input[type=checkbox]:checked+label.slot,
.timeslots input[type=radio]:checked+label.slot {
  background-color: #EEEEEE !important;
  border: 2px solid #dd8e00 !important;
}

/* timeslots - disabled */
.timeslots input[type='radio']:disabled+label.slot {
  color: #a0aec0;
  cursor: not-allowed;
  background-color: #128849 !important;
}

button.primary,
.btn.primary,
.btn-sm.primary,
.btn-md.primary,
.btn-lg.primary,
.bg-primary,
.info-icon.primary::before,
.radio input:checked {
  background-color: #320606 !important;
  border-color: transparent !important;
font-family: arial;
}

.button-container {
  display: flex;
  justify-content: center;
}

button.primary,
lg.primary {
  border-color: transparent;
}

.bg-widget {
  background-color: #f5f5f5 !important;
}

/* the country dropdown */
button[data-action="click->dropdown-menu#toggle"] {
  background-color: transparent !important;
}

/ * the swiper buttons */ .swiper-button-next,
swiper-button-prev {
  color: #333333 !important;
}

.swiper-button-prev {
  margin-left: 6px;
  color: #333333 !important;
}

.swiper-button-next {
  margin-right: 6px;
  color: #333333 !important;
}

.image-description-container {
  display: flex;
justify-content: center;
}

.image-text {
  color: #EEEEEE;
  font-style: italic;
}

.logo {
width: 30%;
  height: 100%;
  object-fit: contain;
}

#results {
  margin-bottom: 72px;
}

 

Javascript mode

- Make sure you have the 'Embedded Tracking Installation' section of the widget config enabled (if the embedded tracking section of the widget isn't enabled for you, please speak with our support team to get it enabled before beginning this tutorial). 

 

- Watch this short video and use this code sample to get started in styling your widget: 

 

 

1) You can watch the video above to learn more about the background styles, element styles, font styling and top image configuration.

2) Copy and paste the code below into the embedded tracking field named 'Embedded Tracking Installation'.

This is where the tutorial might need your marketer or web developer to get involved. 

<script>
// Define variables for font URL and styles
const fontUrl = 'https://fonts.googleapis.com/css2?family=Rowdies:wght@300&display=swap';

// Background styles for elements
const backgroundColorWidget = 'transparent !important';
const backgroundColorLabel = 'grey !important';
const backgroundColorCheckedRadio = 'white !important';
const backgroundColorAppBody = '#E2E2E2CC !important';

// Common styles for the widget
const fontFamilyRowdies = "Rowdies, Arial, Charcoal, sans-serif";
const borderRadius2px = '2px';

// Create the Image
const imgElement = document.createElement('img');
imgElement.src = 'https://i.imgur.com/5A1xXod.png';
imgElement.width = 120;
imgElement.height = 120;

window.onload = function () {
// Create and append a link element for the font
const fontLink = document.createElement('link');
fontLink.href = fontUrl;
fontLink.rel = 'stylesheet';
document.head.appendChild(fontLink);

// Define styles using variables
const styles = `
.widget,
#widget-tabs,
#menu-button,
select,
input[type='text'],
input[type='tel'],
input[type='date'],
textarea,
.bg-widget {
background-color: ${backgroundColorAppBody};
},
label * {
background-color: ${backgroundColorLabel};
},
.timeslots input[type='radio']:checked + label.slot {
background-color: ${backgroundColorCheckedRadio};
},
.dropdown {border-radius: ${borderRadius2px};},
.reveal-multiselect button {border-radius: ${borderRadius2px};},
.reveal-multiselect ul,
.tabs-title > a:focus,
.tabs-title > a:hover {
background-color: ${backgroundColorWidget};
},
input[type='radio']:checked, label.slot, timeslots {
background-color: ${backgroundColorWidget};
},
#app-body {
background-color: ${backgroundColorAppBody};
}
`;

const styleSheet = document.createElement("style");
styleSheet.type = "text/css";
styleSheet.innerText = styles;
document.head.appendChild(styleSheet);
};

// Set common styles for all elements
const allElements = document.querySelectorAll('*');
allElements.forEach(function (element) {
element.style.borderRadius = borderRadius2px;
element.style.backgroundColor = backgroundColorWidget;
element.style.fontFamily = fontFamilyRowdies;
});

// Set background color for specific elements
document.querySelectorAll("body.pg-bg-widget, #app-body").forEach(function (element) {
element.style.backgroundColor = backgroundColorWidget;
});

// Select the .px-4 element
let px4 = document.querySelector('.px-4');

// Check if the px-4 element exists
if (px4) {
// Create a container div for the image
let imgContainer = document.createElement('div');
imgContainer.style.display = 'flex';
imgContainer.style.justifyContent = 'center'; // center the image horizontally
imgContainer.style.marginTop = '10px';
imgContainer.appendChild(imgElement);

// Insert the container div before the "px-4" element
px4.parentElement.insertBefore(imgContainer, px4);
}
</script>

 

Remove time slots on your online waitlist

If you want to hide disabled time slots and only show the available ones, you can do it with this script addition: 

<script> 
  // make it so that waitlist timeslots and text are hidden
  document.addEventListener("DOMContentLoaded", function() {
    if (window.location.href.includes("wait_list")) {
          // Function to hide elements that contain specific text
          function hideElementsContainingText(className, searchText) {
              // Get all elements with the specified class
              var elements = document.querySelectorAll('.' + className.replace(/\s+/g, '.'));

              // Loop through the elements using forEach
              elements.forEach(function(element) {
                  // Check if the element's text includes the desired text
                  if (element.textContent.includes(searchText)) {
                      // Hide the element
                      element.style.display = 'none';
                  }
              });
          }

    // Call the function with the specific class and text
    hideElementsContainingText('px-4 text-xs text-grey-700', 'Select your preferred time');

      // find the timeslots and hide them
      const timeslots = document.querySelectorAll('.timeslots');
      timeslots.forEach(function(timeslot) {
        timeslot.style.display = 'none';
      });

      // Check the first timeslot
      const firstTimeslot = document.querySelector('.timeslots input[type="radio"]');
      if (firstTimeslot) {
        firstTimeslot.checked = true;
      }

      // Enable the continue button
      const continueButton = document.querySelector('button[data-widget--results-target="confirm"]');
      if (continueButton) {
        continueButton.disabled = false;
      }

    }
  });
</script>

 

Lock the continue button to the bottom of the screen when scrolling

// fix the bottom button position when scrolling
document.addEventListener("DOMContentLoaded", function() {
  var containers = document.querySelectorAll('.button-container');


  function applyStyles(container) {
    if (container.querySelector('button')) {
      container.style.position = 'fixed';
      container.style.bottom = '0';
      container.style.left = '0';
      container.style.width = '100%';
      container.style.zIndex = '1000';
      container.style.boxShadow = '0 -2px 5px rgba(0,0,0,0.2)';
    }
    if (container.querySelector('.flex justify-center')) {
      container.style.position = 'fixed';
      container.style.bottom = '0';
      container.style.left = '0';
      container.style.width = '100%';
      container.style.zIndex = '1000';
      container.style.boxShadow = '0 -2px 5px rgba(0,0,0,0.2)';
    }
  }

  Array.prototype.forEach.call(containers, function(container) {
    applyStyles(container);
  });
});

Make sure to add 72px to the bottom of the widget so your scrollable button does not cover text at the bottom of the screen. To make sure that the button text is easy to read on a large screen, you can also use text-align: center and justify-content: center. 

copy and paste the css below and add it to your custom css section:

#results {
  margin-bottom: 72px;
}

.guest-form {
  margin-bottom: 72px;
}

button {
  text-align; center !important;
  justify-content: center !important;
}

 

Show a scrollable gallery for your events, tickets and preferences

Follow this guide here to advertise all your preferences, events and tickets within a scrollable gallery.

Add black text on a highlighted timeslot (for supporting dark mode)

.timeslots input[type=radio]:checked + label.slot {
  color: black !important;
}

Sort all the options / preferences by price

<script>
      document.addEventListener('DOMContentLoaded', function () {
        // Start sort preference
        let preferenceSelectElement = document.getElementById('preference_id');
        if (preferenceSelectElement) {
          if (preferenceSelectElement.type !== 'hidden') {
            // Convert options to an array
            let preferenceSelectOptions = Array.from(
              preferenceSelectElement?.options || []
            );

            // Sort the array based on the 'data-amount' attribute
            preferenceSelectOptions.sort(function (a, b) {
              let aAmount = a.getAttribute('data-amount') || 0;
              let bAmount = b.getAttribute('data-amount') || 0;
              return Number(aAmount) - Number(bAmount);
            });

            // Remove all options
            while (preferenceSelectElement.firstChild) {
              preferenceSelectElement.firstChild.remove();
            }

            // Append the sorted options
            preferenceSelectOptions.forEach(function (option) {
              preferenceSelectElement.appendChild(option);
            });

            let hasSelectedOption = false;
            let params = new URLSearchParams(window.location.search);
            let preferenceId = params.get('preference_id');
            if (!!preferenceId) {
              preferenceSelectOptions.forEach(function (option) {
                if (option?.value === preferenceId) {
                  option.selected = true;
                  hasSelectedOption = true;
                }
              });
            }
            if (!hasSelectedOption) {
              if (preferenceSelectOptions[0]) {
                preferenceSelectOptions[0].selected = true;
              }
            }
          }
        }
        // End sort preference
    });
</script>

Change the text when selecting events and time slots

<script> 
// Convert the HTMLCollection to an array
const elements = Array.from(document.body.getElementsByTagName('*'));

// If you aren't advertising events, change the subtitle text
elements.forEach(function(element) {
    // Check if the element contains the text "Other Events"
    if (element.innerHTML.includes("Other Events")) {
        // Replace "Other Events" with "Packages, Events & Set Menus"
        element.innerHTML = element.innerHTML.replace("Other Events", "Packages, Events & Set Menus");
    }
});

// If 'Select a time' is too basic for you, you can get more creative
elements.forEach(function(element) {
    // Check if the element contains the text "Select a time"
    if (element.innerHTML.includes("Select a time")) {
        // Replace "Select a time" with "What time would you like your experience?"
        element.innerHTML = element.innerHTML.replace("Select a time", "What time would you like your experience?");
    }
});
</script> 

make the logo smaller


.logo {
width: 30%;
  height: 100%;
  object-fit: contain;
}

Create a script which changes the style for a selected day 

<script>

// First, select the element using its class, ID, or any other selector that uniquely identifies it.
var dateSelector = document.querySelector('.search-date-selector');

// Then, use getAttribute to get the value of the data-date attribute.
var selectedDate = dateSelector.getAttribute('data-date');

// Now, create the if statement to check if the selected date is February 14, 2024.
if (selectedDate === '2024-02-14') {
  // Define variables for font URL and styles
  const fontUrl = 'https://fonts.googleapis.com/css2?family=Rowdies:wght@300&display=swap';

  // Background styles for elements
  const backgroundColorWidget = 'transparent !important';
  const backgroundColorLabel = 'grey !important';
  const backgroundColorCheckedRadio = 'white !important';
  const backgroundColorAppBody = '#FFEEF0 !important';

  // Common styles for the widget
  const fontFamilyRowdies = "Rowdies, Arial, Charcoal, sans-serif";
  const borderRadius2px = '2px';

  // Create the Image
  const imgElement = document.createElement('img');
  imgElement.src = '';
  imgElement.width = 120;
  imgElement.height = 120;

  window.onload = function () {
    // Create and append a link element for the font
    const fontLink = document.createElement('link');
    fontLink.href = fontUrl;
    fontLink.rel = 'stylesheet';
    document.head.appendChild(fontLink);

    // Define styles using variables
    const styles = `
       .widget, 
       #widget-tabs, 
       #menu-button, 
       select, 
       input[type='text'],
       input[type='tel'], 
       input[type='date'], 
       textarea, 
       .bg-widget {
         background-color: ${backgroundColorAppBody};
       }, 
       label * {
         background-color: ${backgroundColorLabel};
       },
       .timeslots input[type='radio']:checked + label.slot {
         background-color: ${backgroundColorCheckedRadio};
       },
       .dropdown {border-radius: ${borderRadius2px};},
       .reveal-multiselect button {border-radius: ${borderRadius2px};},
       .reveal-multiselect ul, 
       .tabs-title > a:focus, 
       .tabs-title > a:hover {
         background-color: ${backgroundColorWidget};
       },
       input[type='radio']:checked, label.slot, timeslots {
         background-color: ${backgroundColorWidget};
       }, 
       #app-body {
          background-color: ${backgroundColorAppBody};
       }
    `;

    const styleSheet = document.createElement("style");
    styleSheet.type = "text/css";
    styleSheet.innerText = styles;
    document.head.appendChild(styleSheet);
  };

  // Set common styles for all elements
  const allElements = document.querySelectorAll('*');
  allElements.forEach(function (element) {
    element.style.borderRadius = borderRadius2px;
    element.style.backgroundColor = backgroundColorWidget;
    element.style.fontFamily = fontFamilyRowdies;
  });

  // Set background color for specific elements
  document.querySelectorAll("body.pg-bg-widget, #app-body").forEach(function (element) {
    element.style.backgroundColor = backgroundColorWidget;
  });
}
</script>

 

Change the wording on the payment confirmation page 

 

Change the wording on the confirmation page if the party is large

document.addEventListener('DOMContentLoaded', function() {
    // Function to check if a is greater than b without using the > and && operators
    function isGreaterThan(a, b) {
        // If the absolute difference plus b equals a, and a is not equal to b, then a is greater than b
        return Math.abs(a - b) + b === a;
    }

    // Function to update reservation status based on the number of guests
    function updateReservationStatus(numberOfGuests) {
        // Use the isGreaterThan function for comparison
        if (isGreaterThan(numberOfGuests, 4)) {
            // Find the element that contains the reservation status
            const reservationStatusElement = document.querySelector('.text-xl.font-bold');
            if (reservationStatusElement) {
                // Change the text to 'Reservation pending'
                reservationStatusElement.textContent = 'Reservation pending';
            }

            // Find and remove the image indicating the reservation status
            const reservationImage = document.querySelector('img[alt="Reservation confirmed"]');
            if (reservationImage) {
                reservationImage.parentNode.removeChild(reservationImage);
            }
        }
    }

    // Find the element that contains the number of guests
    const guestElement = document.querySelector('.pt-1');
    if (guestElement) {
        // Extract the number of guests from the element's text
        const guestText = guestElement.textContent || ''; // e.g., "5 guests"
        const numberOfGuests = parseInt(guestText);

        // Call the function to update the reservation status based on the number of guests
        updateReservationStatus(numberOfGuests);
    }
});

 

Removing Venue name at the top of the Widget can be done using simple css.

Add the following in the CSS section of your widget under Look & feel tab.

.flex .heading-1 {
display: none;
}

 

Always show the join waitlist button

Even if you are fully booked, or your waitlist is turned off, or there are no slots available, show the join waitlist button anyway

<script>
document.addEventListener("DOMContentLoaded", function() {
  // Find all existing 'Join waitlist' links
  var waitlistLinks = document.querySelectorAll('.px-4.my-6 a[href*="date="]');
  var existingDates = new Set();

  // Populate a set with dates from existing waitlist links
  waitlistLinks.forEach(function(link) {
    var url = new URL(link.href);
    var date = url.searchParams.get("date");
    existingDates.add(date);
  });

  // Assume we extract a date from a URL parameter or a form field on the page
  var urlParams = new URLSearchParams(window.location.search);
  var dateToAdd = urlParams.get('date'); // Assuming 'date' is a URL query parameter

  if (!dateToAdd) {
    // Fallback or default date if not specified, or you might want to handle this case differently
    dateToAdd = new Date().toISOString().slice(0, 10); // Default to today if no date parameter found
  }

  // Create URL for new 'Join waitlist' link with the selected date
  var newWaitlistUrl = `https://eatapp.co/reserve/the-restaurant-at-buckingham-s-choice-83b28a/wait_list?covers=2&date=${dateToAdd}&locale=en`;

  // Check if a waitlist link with the selected date already exists
  if (!existingDates.has(dateToAdd)) {
    var newHtml = `
      <div class="px-4 my-6">
        <p class="font-semibold text-sm mb-5">Can’t find the time you want?</p>
        <a class="font-semibold text-sm text-primary no-underline cursor-pointer mr-5" href="${newWaitlistUrl}">Join waitlist</a>
      </div>
    `;

    // Select the terms and conditions block
    var termsAndConditions = document.querySelector('.my-6.px-4.text-custom-primary');
    if (termsAndConditions) {
      // Insert the new HTML block above the terms and conditions
      termsAndConditions.insertAdjacentHTML('beforebegin', newHtml);
    }
  } else {
    console.log("A waitlist link for the date " + dateToAdd + " with the same URL already exists.");
  }
});
</script>

Make the cover selector row sticky to the top.

Add the following in the CSS section of your widget under Look & feel tab.

#search {
position:sticky !important;
}

.overflow-auto {
  overflow:visible;
}

 

Hide dates that are not available for a selected preference or shift

Add the following in the embedded tracking section of your widget under Look & feel tab.

<script>
document.addEventListener('DOMContentLoaded', (event) => {
    // Get the select element by its id
    var selectElement = document.getElementById('date');

    // Loop through each option in the select element
    for (var i = 0; i < selectElement.options.length; i++) {
        var option = selectElement.options[i];

        // Check if the option is disabled
        if (option.disabled) {
            // Hide the disabled option
            option.style.display = 'none';
        }
    }
});
</script>

You may also want to remove the shift titles when hiding timeslots that are not available. This is because it looks strange to have shift titles that are visible when there are no timeslots. 

<script> 
document.addEventListener("DOMContentLoaded", function() {
  // Get all time slot sections
  const timeSlotSections = document.querySelectorAll(".timeslots");

  timeSlotSections.forEach(section => {
    // Get all slots in this section
    const slots = section.querySelectorAll("div.flex");

    // Check if all slots are hidden
    const allSlotsHidden = Array.from(slots).every(slot => slot.style.display === "none");

    if (allSlotsHidden) {
      // Get the shift title (the previous sibling of the section)
      const shiftTitle = section.previousElementSibling;

      if (shiftTitle && shiftTitle.classList.contains("font-semibold")) {
        // Remove the shift title
        shiftTitle.style.display = "none";
      }
    }
  });
});
</script>

 

Hide timeslots that are not available for a selected preference or shift

Add the following in the embedded tracking section of your widget under Look & feel tab.

<script>
document.addEventListener('DOMContentLoaded', function() {
  // Select all input elements with the name 'start_time'
  const timeSlotInputs = document.querySelectorAll('input[name="start_time"]');

  // Loop through each time slot input
  timeSlotInputs.forEach(function(input) {
    // Check if the input is disabled
    if (input.disabled) {
      // Hide the parent div that contains the input and label
      input.parentElement.style.display = 'none';
    }
  });
});
</script>