Paperclip Syntax

Basics

You can start writing HTML and CSS as soon as you open up a Paperclip document. Here's an example:

main.pc
⚡️ Preview

Styles are scoped, meaning that they won't leak into other documents (you can still override styles using the class reference syntax), so you're welcome to define style selectors however you want. No special naming conventions needed.

Styling

Styles in Paperclip have a bit more behavior than regular CSS.

Nested rules

Nested rules eliminates some redundancy around defining style selectors.

Syntax:

.parent-rule {
/* equivalent to: .parent-rule .child-rule */
.child-rule {
}
/* equivalent to: .parent-rule--variant */
&--variant-rule {
}
}

Example:

nested-style-demo.pc
⚡️ Preview

& can be used to combine the parent selector in nested rules like so:

nested-combine-demo.pc
⚡️ Preview

Also note that you can nest @media queries like so:

div {
@media screen and (max-width: 400px) {
color: blue;
}
}

Element scoping

Style blocks that are the defined within elements are scoped to that element. For example:

main.pc
⚡️ Preview

The :self selector applies to the parent element that the style block is defined in. It can also be omitted like so:

main.pc
⚡️ Preview

Scoped styles are recommended since they keep your styles & elements together in one spot, which makes them a bit more portable & maintianable if you're looking to move or refactor code.

:self

:self is a special selector that applies to parent elements of style blocks.

:self(selector)

:self([selector]) allows you to select parent elements with [selector]. For example:

main.pc
⚡️ Preview

☝ This is particularly useful for style variants.

:within(ancestor-selector)

:within([ancestor-selector]) allows you to apply styles with an ancestor that has ancestor-selector.

main.pc
⚡️ Preview

@mixin

Style mixins are useful for defining a bundle of style declarations (like color, font-size) that you then can include into style rules.

Syntax:

@mixin mixin-name {
/* style props */
decl-name: decl-value;
/* nested rules */
div {
color: blue;
}
/* takes body of include statement */
@content;
}

Including mixins syntax:

.my-style {
@include mixin-name;
@include mixin-with-content {
display: block;
div {
color: blue;
}
}
}

Example:

mixin-demo.pc
⚡️ Preview

Re-using media queries

Media queries are re-usable in Paperclip by using the following pattern:

/* Define a media mixin */
@mixin desktop {
@media screen and (max-width: 1400px) {
@content;
}
}
div {
/* use media mixin in any selector */
@include desktop {
font-size: 24px;
}
}

@export

The @export rule allows you to export styles to other documents, as well as application code.

Syntax:

@export {
.my-style {
/* styles here */
}
@keyframes my-keyframe {
/* keyframe code here */
}
@mixin my-mixin {
/* styles here */
}
}

Example:

main.pc
styles.pc
⚡️ Preview

Note that you must wrap styles around @export if you want to reference them.

On that note, I'd recommend only exporting things that you need in other documents since export keywords (@export, export) make it clear around what's public & private.

You can reference class names in React code like so:

import * as cx from "classnames";
import * as typography from "./typography.pc";
<div className={cx(
typography.classNames["default-text"]
)}>

Note that .header-text is not exported, so it's not available in our app code.

$class-reference

Paperclip allows you to explicitly reference class selectors, which is helpful if you're looking to reference or overrides styles in other documents.

Syntax:

<div className="$class-name" />
<div className="$imported-doc.class-name" />

Example:

main.pc
atoms.pc
⚡️ Preview

You can also use class references to override component styles.

:global

All style rules are, by default, scoped to the document they're defined in. This ensures that they don't leak & have unintended side-effects. However, there are rare cases when you may need to define a global style rule, such as styling HTML defined outside of Paperclip that doesn't have a way to define a class attribute.

Syntax:

:global(.my-selector-here > div ~ .another-selector) {
name: value;
}

Here's an example that stylizes parts of react-select:

<style>
.wrapper {
/* global for now so that we get tests to pass */
:global(.select__) {
&control {
display: flex;
background: var(--color-background);
/* more declarations here */
&--is-focused {
/* more declarations here */
}
&:hover {
/* more declarations here */
}
}
&value-container,
&single-value,
&multi-value__label,
&input {
/* more declarations here */
}
/* selectors here */
}
}
</style>
<div export component as="Wrapper" className="wrapper">
{children}
</div>

Here's how you use the above styles in React code:

import * as ui from './Select.pc';
// Keep the select styles locked in
<ui.Wrapper>
<DynamicSelect classNamePrefix="select" {...props} />
</ui.Wrapper>

Other global styles

:root and :global CSS properties are applied globally when imported. Here's an example:

demo.pc
styles.pc
⚡️ Preview

It's okay to define :root variables - this is common pattern around theming. Try to avoid :global selectors whenever possible since they leak into other documents, and may result in unintended side-effects. If you need to use :global try to wrap it around a style rule that's scoped to the document. For example:

/* Safer to use */
.container {
:global(body) {
}
}

Import

You can import styles & components from other files.

Syntax:

<import src="./path/to/document.pc" as="unique-namespace" />

Example:

main.pc
pane.pc
atoms.pc
⚡️ Preview

The as keyword is your access point into anything exported by the imported document, like above.

Other examples:

Components

Components are your UI building blocks.

Syntax:

<!-- defining the component -->
<element-name component as="my-component-name">
</element-name>
<!-- using it -->
<my-component-name />

Example:

main.pc
⚡️ Preview

You can define a component from any root element (meaning that it's not a child of any element) by using the syntax above. Any other element that does not have a component attribute is rendered to the screen. Think of those as previews.

Component previews

Anything that doesn't have a component attribute is rendered to the screen, so you can utilize that behavior to see what you're working on. For example:

buttons.pc
styles.pc
⚡️ Preview

Check out the React Todo MVC Example if you're looking for a more extensive demo.

I'd recommend that you render every visual state of your UI in Paperclip since since that will enable you to set up more reliable visual regression tests. Also note that preview elements won't affect your application size since they're not compiled, so you can write previews to your hearts content.

Exporting components

Components can be exported to be used in application code, as well as other documents.

Syntax:

<!-- just add the "export" attribute to any component -->
<div export component as="MyComponent">
</div>

Example:

todos.pc
styles.pc
⚡️ Preview

Here's how we can use this in our React app:

import React, { useRef, useState } from "react";
import * as ui from "./todos.pc"
const TodoApp = () => {
const [todos, setTodos] = useState([
{ completed: true, label: "walk dog" },
{ completed: true, label: "take out trash" }
]);
const onNewInputChange = (event) => {
// code to add new todo item here
}
return <ui.App>
<ui.Header>
<NewItemInput onChange={} />
<ui.List>
{todos.map(({completed, label}, i) => (
<ui.Item
onClick={/* toggle completed handler here */}
completed={completed}>
{label}
<ui.Item>
))}
</ui.List>
</ui.Header>
</ui.App>;
}

We can also use our exported component in other Paperclip documents. Here's an example:

importing-components-demo.pc
todos.pc
styles.pc
⚡️ Preview

☝🏻 This is a pattern is pretty useful - creating various preview components & then using them in other documents to preview your entire application UI. They're removed from your application bundle (so long as you don't use them in app code) because of tree-shaking.

Overriding component styles

You can override styles in other components assuming that a component exposes an attribute that's bound to className.

Syntax:

attributeBoundToClassName="$class-name"

Example:

style-override-demo.pc
message.pc
⚡️ Preview

Check out class references for more information on how to use $.

Using scoped styles

Alternatively, you can overriding your components appearance by using scoped styles like so:

main.pc
⚡️ Preview

Note that you need to provide a className in your component for inline style overrides to work. To see what I'm talking about, try removing the {className?} binding above and see what happens!

☝🏻 this approach keeps your overrides together, which I find a bit easier to read. The other benefit to this approach is that your code remains portable since everything's in one spot -- super nice for refactoring.

Changing the tag name

You may want to change the native tag name of a component. An example of this is a Button component that may be a button or a tag.

Syntax

<button export component as="Button" {tagName?}>
{children}
</button>

Example

demo.pc
⚡️ Preview

Bindings

Bindings allow you to define dynamic behavior in components.

Child bindings

Syntax:

<div component as="MyComponent">
<!-- reserved keyword - takes element children. -->
{children}
<!-- can be defined via attributes -->
{anotherSlot}
</div>

Example:

main.pc
⚡️ Preview

There will probably be the case where you want to define multiple areas of a component for children to go into. Here's an example of that:

main.pc
styles.pc
⚡️ Preview

{title} and {controls} (and technically also {children}) are considered slots for child nodes to go into, and they can be filled in via attributes:

<Pane
title={<span>some title</span>}
controls={<button>A button</button>}>
Content children
</Pane>

Attribute bindings

Attributes allow you to define dynamic component properties. For example:

buttons.pc
styles.pc
typography.pc
⚡️ Preview

Bindings can also be defined outside of string attributes. For example:

<div component as="Test">
<span className="title" ref={spanRef}>
{title}
</span>
{children}
</div>

Ref here is specific to React around referencing DOM nodes.

You can also use the shorthand approach like so:

<div component as="Test" {ref}>
<span className="title">
{title}
</span>
{children}
</div>

This is particularly useful for making your code more DRY. For example:

<input export component as="Input"
{onChange?}
{defaultValue}
{value}
/>

Optional bindings

By default, bindings are required. So if you define {className} on an element, that property will be required when compiled into application code. To make a binding optional, just add a ? after the binding name like so:

<div component as="Message" {className?}>
{children}
</div>

☝🏻 Here, className is optional, whereas children is not. When compiled to TypeScript, here's what you get:

/* other generated code here */
type MessageProps = {
className?: Function,
children: ReactNode,
};
export const Message: React.FC<MessageProps>;

For more information around type safety, take a look at the type safety doc.

Variant styles

The variant style syntax allows you to apply classes based on component properties - useful for creating variants.

Syntax:

<div component as="MyComponent" className:variant-name="class-name">
</div>
<!-- Usage -->
<MyComponent variant-name />

Example:

main.pc
⚡️ Preview

Fragments

Fragments are useful if you want to render a collection of elements. For example:

main.pc
⚡️ Preview

You can also define components from them:

<fragment component as="Items">
<li>Item</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
</fragment>
<ul>
<Items />
</ul>