Skip to main content Sidebar Design tokens Components Layout Style utilities

We’d love to hear from you. Please reach out to us with any questions or inquiries you may have.

You can contact us via e‐mail at placer.coc.reports+contact@gmail.com.

We look forward to hearing from you!

Got it!
Skip to table of contents

The Placer Toolkit alpha has arrived! 🎉
Plenty of changes are coming your way—some big, some breaking, some even nuclear! Beware the changelog…

Bring it on!

    Contributing

    Placer Toolkit is an open‐source project, and we welcome contributions from everyone. When you join our community, you’ll find a friendly group of enthusiasts at all experience levels, ready to chat about anything and everything related to Placer Toolkit.

    The easiest way to start contributing is to join the discussion forum. This is where you can discuss new ideas, ask for feedback and more!

    A common misconception about contributing to an open‐source project is that you need to know how to code. This simply isn’t true. In fact, there are many ways to contribute, and some of the most important contributions come from those who never write a single line of code. Here’s a list of ways you can make a meaningful contribution to the project:

    • Submitting well‐written bug reports.
    • Submitting feature requests that are within the scope of the project.
    • Improving the documentation.
    • Responding to users that need help in the discussion forum.
    • Triaging issues on GitHub.
    • Being a developer advocate for the project.
    • Sponsoring the project financially.
    • Sharing ideas.
    • Fixing violations of the Placer Style Guide.
    • And of course, contributing code.

    Please take a moment to review these guidelines to make the contribution process as easy as possible for both yourself and the project’s maintainers.

    Using the issue tracker#

    The issue tracker is for bug reports, feature requests and pull requests exclusively. We want you to follow these guidelines when using the issue tracker.

    • Please do not use the issue tracker for personal support requests. Use the discussion forum instead.
    • Please do not derail, hijack or troll issues. Keep the discussion on topic and be respectful of others.
    • Please do not post comments with “+1” or “👍”, as that will spam the comments section with irrelevant comments. Use reactions instead.
    • But do use the issue tracker for bug reports, feature requests and pull requests.

    Issues that do not follow these guidelines are subject to closure. There simply aren’t enough resources for the author and contributors to troubleshoot personal support requests.

    Bug reports#

    A bug is a demonstrable problem caused by code in the library. Bug reports are an important contribution to the quality of the project. When submitting a bug report, there are a few steps you can take to make sure your issues get attention quickly.

    • Please do not paste in large blocks of irrelevant code.
    • But do search for an existing issue before creating a new one.
    • Also explain the bug clearly.
    • And always add a minimal sandbox that demonstrates the bug, like CodePen or JSFiddle.
    • To replicate the bug, do provide additional information when necessary.

    A minimal test case is critical to a successful bug report. It demonstrates that the bug exists in the library and not in surrounding code. Contributors should be able to understand the bug without studying your code, otherwise, they’ll probably move on to another bug.

    Feature requests#

    Feature requests can be added using the discussion forum.

    • Please do search for an existing request before suggesting a new feature.
    • But also use the voting buttons to vote for a feature.
    • Also share substantial use cases and perspective that support new features if they haven’t already been mentioned.
    • And as always, never bump, spam or ping contributors to prioritise your own feature.

    Pull requests#

    To keep the project on track, please consider the following guidelines before submitting a PR.

    • Please do not submit a PR without opening an issue first, unless the changes are trivial (e.g., fixing typos or outdated docs). This may prevent you from doing work that won’t be accepted for various reasons (e.g., someone is already working on it, it’s not a good fit for the project’s roadmap, it needs additional planning, etc.).
    • But do make sure your PR clearly defines what you’re changing. Even if you feel your changes are obvious, please explain them so other contributors can more easily review your works. PRs without detailed descriptions are subject to closure pending more details.
    • Also open your PR against the main branch.
    • Never edit anything in cdn and dist folders. These files are generated automatically, so you must edit the source files instead.

    The author reserves the right to reject any PR that’s outside the scope of the project or doesn’t meet code quality standards.

    Developing#

    To set up a local development environment, fork the repo on GitHub or clone it locally. Then, you can install its dependencies.

    git clone https://github.com/placer-toolkit/placer-toolkit
    cd placer-toolkit # Navigate into your clone
    npm install # Installs dependencies for Placer Toolkit
    cd docs
    npm install # Installs dependencies for Placer Toolkit’s docs

    Once you’ve cloned the repo, you can run the following command to spin up the development server for the docs. Make sure you’re in the docs folder of the project.

    npm run dev # To spin up the dev server provided by Astro
    npx astro dev --host # If you want to expose the server on your network

    This will spin up the dev server on localhost:4321. There isn’t any hot module reloading as Web Components aren’t compatible with this technology, but it’ll automatically refresh the page instead.

    Creating new components#

    To properly scaffold a new component, you must follow a series of steps. Due to the complexity of the documentation structure, these instructions are intended to guide you through the process and ensure your component is integrated correctly.

    The first step is to create the component files. Inside the src/components directory, create a new folder named after your component using kebab‐case. Within this folder, you must create three essential files: a main entry file, a component logic file and a CSS file. Remember to add JSDoc to your component code to ensure that API documentation tables can be auto‐generated properly. Refer to existing components to maintain consistent coding conventions.

    Once the files are created, you must add the component export to the codebase. Open src/placer.ts and add an export for the new component under the Components section. It is essential to ensure that the new entry is alphabetically sorted to maintain order.

     export { default as PcCheckbox } from "./components/checkbox/checkbox.js";
     export { default as PcComparer } from "./components/comparer/comparer.js";
     export { default as PcCopyButton } from "./components/copy-button/copy-button.js";
    +export { default as PcComponentName } from "./components/component-name/component-name.js";
     export { default as PcDetails } from "./components/details/details.js";
     export { default as PcDialog } from "./components/dialog/dialog.js";
     export { default as PcDivider } from "./components/divider/divider.js";

    Finally, you can proceed with documenting the component. Create a new MDX file for your component in docs/src/content/components and adding a link to the component in both docs/src/layouts/Sidebar.astro and docs/src/layouts/MobileSidebar.astro, again, ensuring the entries are alphabetically sorted.

    Following these steps will ensure your new component is properly integrated and its documentation is consistent with the rest of the project.

    Documentation#

    Maintaining good documentation can be a painstaking task, but poor documentation leads to frustration and makes the project less appealing to users. Fortunately, writing documentation for Placer Toolkit is fast and easy!

    Most of Placer Toolkit’s technical documentation is generated with JSDoc comments and TypeScript metadata from the source code. Every property, method, event and other stuff is documented this way. In‐code comments encourage contributors to keep the documentation up to date as changes occur so the docs are less likely to become stale. Refer to an existing component’s code to see how JSDoc comments are used in Placer Toolkit.

    Instructions, code examples and interactive demos are hand‐curated to give users the best possible experience. Typically, the most relevant information is shown first and less common examples are shown towards the bottom. Edge cases and gotchas should be called out in context with tips or warnings.

    The docs are powered by Astro. Check out the docs/src/content/components/*.mdx to get an idea of how pages are structured and formatted. If you’re creating a new component, it may help to use an existing component’s MDX file as a template.

    If you need help with documentation, feel free to reach out in the discussion forum.

    GitHub-flavored Markdown#

    The Placer Toolkit documentation uses MDX with the @astrojs/mdx integration. With this integration, it automatically converts straight to smart quotes with Smartypants and other stuff. It supports standard Markdown from the GFM spec as well as JSX expressions.

    Code blocks#

    For code blocks, use the fenced code block syntax. Do not use the indented code block syntax, as that looks weird in the code, does not support syntax highlighting and also does not use the correct code block styling.

    Code demos#

    Code demos are used to show demonstrations of different states of a component. To add one, use the fenced code block syntax with the language set to demo. All code demos must use HTML, no other language is accepted. JavaScript and CSS can be embedded in the code demo with <script> and <style> tags.

    ```demo
    <!-- insert your HTML code here… -->
    ```

    You can also append attributes to expand the code by default or remove the CodePen button. The order of these attributes don’t matter.

    ```demo:no-codepen:open
    <!-- insert your HTML code here… -->
    ```

    Callouts#

    Callouts can be added using the Callout component. Refer to its docs to add it into the MDX file.

    Details#

    To provide additional details that can be expanded or collapsed, use the Details component.

    <pc-details summary="Details title">
        These details can be shown or hidden!
    </pc-details>

    Auto‐keyboard switcher#

    To switch from Ctrl and depending on which operating system you’re on, import the Astro component from the MDX file.

    {/* The import path may be different depending on where the MDX file is located. */}
    import AutoKeyboardSwitch from "../../components/AutoKeyboardSwitch.astro";

    Then, insert the component in your MDX file like this.

    <AutoKeyboardSwitch keyName="ctrl" />

    It would map to the correct symbol depending on the operating system you’re on.

    There are are many keys that can be remapped. Here’s a list of all the available key names you can use on this component.

    • meta
    • ctrl
    • command (alias of ctrl)
    • enter
    • alt
    • altgr
    • shift
    • tab
    • delete

    GitHub issues#

    To link to a GitHub issue, PR or discussion, you can use this custom Markdown syntax. Make sure to replace #167 with the correct issue, PR or discussion number.

    [github#167]

    Best practices#

    The following is an exhaustive list of conventions, patterns and best practices we try to follow. As a contributor, we ask you make a good faith effort to follow them as well. This ensures consistency and maintainability throughout the project.

    If in doubt, use your best judgement and the maintainers will be happy to guide you during the code review process.

    This section can be a lot to digest in one sitting, so don’t feel like you need to take it all in right now. Most contributors will be better off skimming this section and reviewing the relevant content as needed.

    Accessibility#

    Placer Toolkit is built with accessibility in mind. Creating generic components that are fully accessible to users with varying capabilities across a multitude of circumstances is a daunting challenge. Oftentimes, the solution to an accessibility problem is not written in black and white, and therefore, we may not get it right the first time around. There are, however, guidelines we can follow in our effort to make Placer Toolkit an accessible foundation from which applications and websites can be built.

    We take this commitment seriously, so please ensure your contributions have this goal in mind. If you need help with anything accessibility‐related, please reach out to the community for assistance. If you discover an accessibility concern within the library, please file a bug on the issue tracker.

    It’s important to remember that, although accessibility starts with foundational components, it doesn’t end with them. It’s everyone’s responsibility to encourage best practices and ensure we’re providing an optimal experience for all of our users.

    Code formatting#

    Most code formatting is handled by Prettier via commit hooks. However, for the best experience, you should install it in your editor and enable Format on Save.

    Please do not make any changes to .prettierrc without consulting the maintainers.

    Language#

    In Placer Toolkit, we consistently follow the Placer Style Guide and use British English. We kindly ask that contributors follow the same guidelines when contributing to the project.

    Composability#

    Components should be composable, meaning you can easily reuse them with and within other components. This reduces the overall size of the library, expedites feature development and maintains a consistent user experience.

    Component structure#

    All components have a host element, which is a reference to the pc-* element itself. Make sure to always set the host element’s display property to the appropriate value depending on your needs, as the default is inline per the custom element spec.

    :host {
        display: block;
    }

    Aside from display, avoid setting styles on the host element when possible. The reason for this is that styles applied to the host element are not encapsulated. Instead, create a base element that wraps the component’s internals and style that instead.

    When authoring components, please try to follow the same structure and conventions found in other components. Classes, for example, generally follow this structure.

    1. Static properties/methods
    2. Private/public properties (that are not reactive)
    3. @query decorators
    4. @state decorators
    5. @property decorators
    6. Lifecycle methods (connectedCallback(), disconnectedCallback(), firstUpdated(), etc.)
    7. Private methods
    8. @watch decorators
    9. Public methods
    10. The render() method

    Please avoid using the public keyword for class fields. It’s simply too verbose when combined with decorators, property names and arguments. However, please do add private in front of any property or method that is intended to be private.

    This might seem like a lot, but it’s fairly intuitive once you start working with the library. However, don’t let this structure prevent you from submitting a PR. Code can change and nobody will chastise you for “getting it wrong”. At the same time, encouraging consistency helps keep the library maintainable and easy for others to understand.

    Class names#

    All components use a shadow DOM, so styles are completely encapsulated from the rest of the document. As a result, class names used inside a component won’t conflict with class names outside the component, so we’re free to name them anything we want.

    Boolean properties#

    Boolean properties should always default to false, otherwise, there’s no way for the user to unset them using only attributes. To keep the API as friendly and consistent as possible, use a property such as noHeader and a corresponding kebab‐case attribute such as no-header.

    When naming boolean properties that hide or disable things, prefix them with no-, e.g., no-spin-buttons and avoid using other verbs such as hide- or disable- for consistency.

    Conditional slots#

    When a component relies on the presence of slotted content to do something, don’t assume its initial state is permanent. Slotted content can be added or removed any time and components must be aware of this. A good practice to manage this is:

    • Adding @slotchange=${this.handleSlotChange} to the slots you want to watch.
    • Add a handleSlotChange method and use the hasSlot utility to update state variables for the respective slot(s).
    • Never conditionally render <slot> elements in a component—always use hidden so the slot remains in the DOM and the slotchange event can be captured.

    See the source of the Card, Dialog and Drawer components for examples.

    Dynamic slot names and expand/collapse icons#

    A pattern has been established in <pc-details> and the upcoming <pc-tree-item> components for expand/collapse icons that animate on open/close. In short, create two slots called expand-icon and collapse-icon and render them both in the DOM, using CSS to show/hide only one based on the current open state. Avoid conditionally rendering them. Also avoid using dynamic slot names, such as <slot name=${open ? "open" : "closed"}>, because Firefox will not animate them.

    There should be a container element immediately surrounding both slots. The container should be animated with CSS by default and it should have a part so the user can override the animation or disable it. Please refer to the source and documentation for the Details component for examples.

    Fallback content in slots#

    When providing content inside of <slot> elements, avoid adding parts.

    <!-- ❌ Don’t do this -->
    <slot name="icon">
        <pc-icon part="close-icon"></pc-icon>
    </slot>

    This creates confusion because the part will be documented, but it won’t work when the user slots in their own content. The recommended way to customise this example is for the user to slot in their own content an target its styles with CSS as needed.

    Custom events#

    Components must only emit custom events, and all custom events must start with pc- as a namespace. For compatibility with frameworks that utilise DOM templates, custom events must have lowercase, kebab‐case names. For example, use pc-change instead of pcChange.

    This convention avoids the problem of browsers lowercasing attributes, causing some frameworks to be unable to listen to them. This problem isn’t specific to one framework, but Vue’s documentation provides a good explanation of the problem.

    Change events#

    When change events are emitted by Placer Toolkit components, they should be named pc-change and they should only be emitted as as result of user input. Programmatic changes, such as setting element.value = "…" should not result in a change event being emitted. This is consistent with how native form controls work.

    CSS custom properties#

    To expose custom properties as part of a component’s API, scope them to the :host block.

    :host {
        --background-color: var(--pc-color-primary-fill-loud);
        --color: var(--pc-color-primary-on-loud);
    }

    Then use the following syntax for comments so they appear in the auto‐generated API tables. Do not use the --pc- prefix, as that is reserved for design tokens that live in the global scope.

    /**
     * @cssproperty --background-color: var(--pc-color-primary-fill-loud) - The component’s background colour.
     * @cssproperty --color: var(--pc-color-primary-on-loud) - The component’s text colour.
     */
    @customElement("pc-example")
    export class PcExample extends PlacerElement {
        // …
    }

    Focusing on disabled items#

    When an item focusable with keyboard navigation is disabled (e.g., tabs, trees, menu items, etc.), the disabled item should not receive focus via keyboard, clicks or taps. It should be skipped just like in operating system menus and in native HTML form controls. There is no exception to this. If a particular item requires focus for assistive devices to provide a good user experience, the item should not be disabled and, upon activation, it should inform the user why the respective action cannot be completed.

    Properties vs CSS custom properties#

    When designing a component’s API, standard properties are generally used to change the behaviour of a component, whereas CSS custom properties (“CSS variables”) are use to change the appearance of a component. Remember that properties can’t respond to media queries, but custom properties can.

    There are some exceptions to this (e.g., when it significantly improves DX), but a good rule of thumbs is, “Will this need to change based on screen size?” If so, you probably want to use a CSS custom property.

    CSS custom properties vs CSS parts#

    There are two ways to customise components: With CSS custom properties (“CSS variables”) or CSS parts (“parts”).

    CSS custom properties are scoped to the host element and can be reused throughout the component. A good example of a custom property would be --border-width, which might get reused throughout a component to ensure borders share the same width for all internal elements.

    CSS parts let you target a specific element inside the component’s shadow DOM, but by design, you can’t target a path’s children or siblings. You can only customise the part itself. Use a part when you need to allow a single element inside the component to accept styles.

    This convention can be relaxed when following it strictly would reduce developer experience.

    Naming CSS parts#

    While CSS parts can virtually be named anything, within Placer Toolkit, they must use the kebab‐case convention and lowercase letters.

    When composing elements, use part to export the host element and exportparts to export its parts.

    @customElement("pc-example")
    export class PcExample extends PlacerElement {
        // …
    
        render() {
            return html`
                <div part="base">
                    <pc-icon
                        part="icon"
                        <!-- other attributes… -->
                        exportparts="base:icon-base"
                    ></pc-icon>
                </div>
            `;
        }
    }

    This results in a consistent, easy to understand structure for parts. In this example, the icon part will target the host element and the icon-base part will target the icon’s base part.

    Dependencies#

    TL;DR: A component counts as a dependency only when it’s rendered inside another component’s shadow root.

    Many components within Placer Toolkit depend on others internally. For example, <pc-dialog> uses <pc-button> for the close button in the dialog panel. Since this component appears in the button’s shadow root, they are considered dependencies of Dialog. Since dependencies are automatically loaded, users only need to import Dialog and everything will work as expected.

    Contrast this to <pc-select> and <pc-option>. At first, one might assume that Option is a dependency of Select. After all, you can’t really use Select without slotting in at least one Option. However, Option is not a dependency of Select! The reason is because no Option is rendered in the Select’s shadow root. Since the options are provided by the user, it’s up to them to import both components independently.

    Some may suggest, that Placer Toolkit should auto‐load Select and Option, Tab Group, Tab and Tab Panel, etc. Although some components are designed to work together, they’re technically not dependencies so eagerly loading them may not be desirable. What if someone wants to roll in their own component with a superset of features? They wouldn’t be able to if Placer Toolkit automatically imported it!

    For non‐dependencies, the user should decide what gets registered, even if it comes with a minor inconvenience.

    Form controls#

    Form controls should support submission and validation through the following conventions:

    • All form controls must use name, value and disabled properties in the same manner as HTMLInputElement.
    • All form controls must have a setCustomValidity() method so the user can set a custom validation message.
    • All form controls must have a reportValidity() method that report their validity during form submission.
    • All form controls must have an invalid property that reflects their validity.
    • All form controls should mirror their native validation attributes such as required, pattern, minlength, maxlength, etc. when possible.
    • All form controls must be tested to work with the standard <form> element.

    System icons#

    Avoid inlining SVG icons inside of templates. If a component requires an icon, make sure <pc-icon> is a dependency of the component and use the system library.

    <pc-icon library="system" icon-style="…" name="…"></pc-icon>

    This will render the icons instantly whereas the default library would fetch them from a remote source. If an icon isn’t available in the system library, you will need to add it to library.system.ts. Using the system library ensures that all icons load instantly and are customisable by users who wish to provide a custom resolver for the system library.