Skip to main content Design tokens Components Style utilities
Skip to table of contents

    Usage

    Placer components are just regular HTML elements, or custom elements to be precise. You can use them like any other element. Each component has detailed documentation that describes its full API, including properties, events, methods and more.

    If you’re new to custom elements, often referred to as “web components”, this section will familiarise you with how to use them.

    Attributes and properties

    Many components have properties that can be set using attributes. For example, buttons accept a size attribute that maps to the size property which dictates the button’s size.

    <pc-button size="small">Click me</pc-button>

    Some properties are boolean, so they only have true/false values. To activate a boolean property, add the corresponding attribute without a value.

    <pc-button disabled>Click me</pc-button>

    In rare cases, a property may require an array, an object or a function. For example, to customise the colour picker’s list of preset swatches, you set the swatches property to an array of colours. This must be done with JavaScript.

    <pc-color-picker></pc-color-picker>
    
    <script>
      const colorPicker = document.querySelector("pc-color-picker");
    
      colorPicker.swatches = ["red", "orange", "yellow", "green", "blue", "purple"];
    </script>

    Refer to a component’s documentation for a complete list of its properties.

    Events

    You can listen for standard events such as click, mouseover, mouseout, etc. as you normally would. However, it’s important to note that many events emitted within a component’s shadow root will be re‐targeted to the host element. This may result in, for example, multiple click handlers executing even if the user clicks just once. Furthermore, event.target will point to the host element, making things even more confusing.

    As a result, you should almost always listen for custom events instead. For example, instead of listening to click to determine when a <pc-checkbox> gets toggled, listen to pc-change.

    <pc-checkbox>Check me</pc-checkbox>
    
    <script>
      const checkbox = document.querySelector("pc-checkbox");
    
      checkbox.addEventListener("pc-change", (event) => {
          console.log(event.target.checked ? "checked" : "not checked");
      });
    </script>

    All custom events are prefixed with pc- to prevent collisions with standard events and other libraries. refer to a component’s documentation for a complete list of its custom events.

    Methods

    Some components have methods you can call to trigger various behaviours. For example, you can set focus on a Placer input using the focus() method.

    <pc-input></pc-input>
    
    <script>
      const input = document.querySelector("pc-input");
    
      input.focus();
    </script>

    Refer to a component’s documentation for a complete list of its methods and their arguments.

    Slots

    Many components use slots to accept content inside of them. The most common slot is the default slot, which includes any content inside the component that doesn’t have a slot attribute.

    For example, a button’s default slot is used to populate its label.

    <pc-button>Click me</pc-button>

    Some components also have named slots. A named slot can be populated by adding a child element with the appropriate slot attribute. Notice how the icon below has the slot="prefix" attribute? This tells the component to place the icon into its prefix slot.

    <pc-button>
      <pc-icon
          library="default"
          icon-style="solid"
          name="gear"
          slot="prefix"
      ></pc-icon>
      Settings
    </pc-button>

    The location of a named slot doesn’t matter. You can put it anywhere inside the component and the browser will move it to the right place automatically!

    Refer to a component’s documentation for a complete list of available slots.

    Don’t use self‐closing tags

    Custom elements cannot have self‐closing tags. Similar to <script> and <textarea>, you must always include the full closing tag.

    <!-- ❌ Don’t do this -->
    <pc-input />
    
    <!-- ✔️ Always do this -->
    <pc-input></pc-input>

    Differences from native elements

    You might expect similarly named elements to share the same API as native HTML elements, but this isn’t always the case. Placer components are not designed to be one‐to‐one replacements for their HTML counterparts. While they usually share the same API, there may be subtle differences.

    For example, <button> and <pc-button> both have a type attribute, but the native one defaults to submit while the Placer one defaults to button since this is a better default for most users.

    Don’t make assumptions about a component’s API!
    To prevent unexpected behaviours, please take the time to review the documentation and make sure you understand what each attribute, property, method and event is intended to do.

    Waiting for components to load

    Web Components are registered with JavaScript, so depending on how and when you load Placer Toolkit, you may notice a Flash of Undefined Custom Elements (FOUCE) when the page loads. There are a couple ways to prevent this, both of which are described in the linked article.

    One option is to use our FOUCE utility to prevent FOUCE from happening. This may not work if you load all components at once.

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/placer-toolkit@0.5.1/dist/style-utilities/fouce.css" />

    You also have to add the pc-cloak class to the <html> element so it applies the FOUCE utility.

    Another option is to use the :defined CSS pseudo‐class to “hide” custom elements that haven’t been registered yet. You can scope it to specific tags or you can hide all undefined custom elements as shown below.

    :not(:defined) {
      visibility: hidden;
    }

    As soon as a custom element is registered, it will immediately appear with all of its styles, effectively eliminating FOUCE. Note the use of visibility: hidden instead of display: none to reduce shifting as elements are registered. The drawback to this approach is that custom elements can potentially appear one by one instead of all at the same time. This is why we recommend using the FOUCE utility.

    There’s still another option though. You can use customElements.whenDefined(), which returns a promise that resolves when the specified element gets registered. You’ll probably want to use it with Promise.allSettled() in case an element fails to load for some reason.

    A clever way to use this method is to hide the <body> with opacity: 0 and add a class that fades it in as soon as all your custom elements are defined.

    <script>
      await Promise.allSettled([
          customElements.whenDefined("pc-button"),
          customElements.whenDefined("pc-card"),
          customElements.whenDefined("pc-rating"),
      ]);
    
      /* The button, card and rating components are registered now!
         Add the “ready” class so the UI fades in. */
      document.body.classList.add("ready");
    </script>
    
    <style>
      body {
          opacity: 0;
          transition: opacity var(--pc-transition-slow) step-end;
      }
    
      body.ready {
          opacity: 1;
      }
    </style>

    Component rendering and updating

    Placer components are built with Lit, a library that makes authoring custom elements easier, more maintainable and a lot more fun compared to vanilla Web Components. Here is some helpful information about rendering and updating you should probably be aware of.

    To optimise performance and reduce re‐renders, Lit batches component updates. This means changing multiple attributes or properties at the same time will result in just a single re‐render. In most cases, this isn’t an issue, but there may be times you’ll need to wait for the component to update before continuing.

    Consider this example. We’re going to change the checked property of the checkbox and observe its corresponding checked attribute, which happens to reflect.

    const checkbox = document.querySelector("pc-checkbox");
    
    checkbox.checked = true;
    
    console.log(checkbox.hasAttribute("checked")); // Returns false

    Most developers will expect this to be true instead of false, but the component hasn’t had a chance to re‐render yet so the attribute doesn’t exist when hasAttribute() is called. Since changes are batched, we need to wait for the update before proceeding. This can be done using the updateComplete property, which is available on all Lit‐based components.

    const checkbox = document.querySelector("pc-checkbox");
    
    checkbox.checked = true;
    
    checkbox.updateComplete.then(() => {
      console.log(checkbox.hasAttribute("checked")); // Returns ""
    });

    This time, we see an empty string, which means the boolean attribute is now present!

    Avoid using setTimeout() or requestAnimationFrame() in situations like this. They might work, but it’s far more reliable to use updateComplete instead.