(WIP)
The Common-Styling module provides a reusable design system for platformOS to make your projects look great out of the box and gives you simple tools to customize them.
It includes reusable CSS and JavaScript that are (or will be) leveraged by platformOS modules, and which you can also use directly in your own projects. The idea is to provide a consistent, documented way of building modules that look good from the start and which you can easily customize to fit your needs.
Common-styling follows the platformOS DevKit best practices.
- ⚙️ Customizable through CSS variables defined in
pos-config.css
- 📖 Built-in
/style-guide
page to preview components and check available variables - 🧩 Provides shared CSS and JavaScript utilities for use in platformOS modules and projects
- 📝 Includes base styles for forms, buttons, typography, and other common components
- 🛡️ Scoped with
pos-
prefixes and CSS layers to avoid conflicts with project code - 🌙 Supports both light and dark themes (automatic or manual)
- 📱 Responsive styles included by default
- ✅ Follows platformOS DevKit best practices
The platformOS Common Styling Module is available on the Partner Portal Modules Marketplace.
After installation, visit <your instance url>/style-guide
to preview all available components, see examples of forms, buttons, typography and the complete list of CSS variables you can override.
Before installing the module, ensure that you have pos-cli installed. This tool is essential for managing and deploying platformOS projects.
The platformOS Common Styling is fully compatible with platformOS Check, a linter and language server that supports any IDE with Language Server Protocol (LSP) integration. For Visual Studio Code users, you can enhance your development experience by installing the VSCode platformOS Check Extension.
-
Navigate to your project directory where you want to install the Common Styling Module.
-
Run the installation command:
pos-cli modules install common-styling
This command installs the Common Styling Module and updates or creates the app/pos-modules.json
file in your project directory to track module configurations.
- Download the source code into your local environment:
pos-cli modules download common-styling
- Explore the built-in Style Guide.
After installation, visit
/style-guide
on your instance. (Make sure you deploy the module usingpos-cli deploy
command)
- Preview all available components.
- See examples of forms, buttons, and typography.
- Find the complete list of CSS variables you can override.
-
Install the module using the pos-cli.
-
Include the following partial into your layout's
<head>
section:
{% render 'modules/common-styling/init' %}
👉 If you do not have a layout, check the style-guide's layout for an example of a minimal layout.
- Optionally enable the CSS reset. It resets default browser styling and fixes some browser-specific issues
- It’s safe to use in a fresh app.
- In an existing app, enabling it might cause unexpected changes.
To use it, pass the reset: true
parameter to the render tag mentioned above and use a pos-app
class anywhere on your main content container:
{% render 'modules/common-styling/init', reset: true %}
- Scope the styling with
pos-app
:
- To apply common-styling globally, add
class="pos-app"
to your application’s<html>
tag:
<html lang="en" class="pos-app">
- To apply styles only in certain parts of your app, wrap content in a container:
<div class="pos-app">
</div>
Note: If you plan to add your own CSS overrides, always load them after
common-styling
in your layout. This way, your styles take precedence over the defaults.
When using the common-styling
module, you can configure the look of components by overriding the CSS variables defined in pos-config.css
.
Define overrides inside :root {}
so they apply globally across your app.
Instead of editing the module directly, you can override only the variables you need in your own stylesheet.
👉 Copy only the variables you want to change into your app’s CSS file, and redefine them there.
👉 Use /style-guide
as your main reference for available variables and component classes. This page lists all color variables, form components, buttons, and more. It’s the recommended way to discover what you can override.
👉 Common-styling is responsive out of the box. To test layouts, use your browser’s responsive design mode (DevTools → device toolbar).
Add a file to your project, for example:
app/assets/<my-app-name>-config.css
This is where you’ll put your overrides.
In your layout (for example: application.liquid
), include your stylesheet below the common-styling
init. This ensures your overrides take precedence.
{% render 'modules/common-styling/init' %}
<link rel="stylesheet" href="{{ 'variables.css' | asset_url }}">
Copy only the variables you want to change from pos-config.css
into your stylesheet, and redefine them under :root
.
Example — giving primary buttons a green theme:
:root {
--pos-color-button-primary-background: #008000;
--pos-color-button-primary-hover-background: #00a000;
}
- Don’t hardcode values like colors or sizes (with very few exceptions). Always rely on the provided CSS variables.
- Override only what you need — don’t copy the full config.
- You can use CSS functions like
calc()
,from-color()
, orcolor-mix()
if you need to adjust variables dynamically. - Stick to the provided variable system, which maps to our Figma design kit.
- If your app supports dark mode, remember to override both light and dark variables.
- Use
/style-guide
to preview available variables and confirm your overrides.
The common-styling
module includes two base themes by default: a light and a dark one.
To enable automatic dark mode (switches based on the user’s system preference), add the following class to the root <html>
tag in your layout:
<html class="pos-app pos-theme-darkEnabled">
If you want to control the theme manually, use:
<html class="pos-app pos-theme-dark">
This forces the dark theme regardless of system settings.
- Both light and dark themes use the same set of CSS variables. If you override variables, make sure to provide values for both themes if needed.
- You can preview dark mode in your browser using dev tools, toggling system preferences, or manually applying
.pos-theme-dark
. - Example — overriding variables just for dark mode:
:root {
--pos-color-dark-button-primary-background: #004d00;
}
To avoid conflicts and keep styles predictable, all CSS in this module follows a scoping convention.
When naming your module CSS files, please prefix them with pos-
for consistency.
- Files: Prefix module CSS files with
pos-
(e.g.,pos-form.css
,pos-button.css
) for consistency. - Classes: Prefix CSS classes with
pos-
(e.g.,.pos-form
,.pos-button
). This ensures styles fromcommon-styling
won’t interfere with unrelated CSS in your project.
👉 Keep in mind that the module can be used in various contexts, so any styling needs to be scoped just to the module code.
All styles from common-styling
are placed inside a dedicated CSS layer.
This lowers specificity so you can override them easily in your own stylesheet, without having to worry about the selectors used.
Some CSS rules will be inherited when the parent container has a specific class. For example, the .pos-form
scopes styling so that inputs, buttons, and other form-related elements inside the container adopt the design system styles.
Each component should have its own separate CSS file. This keeps the codebase modular, easier to maintain, and consistent across projects.
For example:
pos-form.css
for formspos-button.css
for buttonspos-card.css
for cards
Use ESM Modules to build JavaScript.
The modules should not pollute the global JavaScript namespace. For this purpose, we are using the pos
namespace attached to the global window
object. Extending the namespace is the preferred way to store all the needed data, translations, and module public interface.
There are several basic objects used across the modules that could be extended for consistency. Those are shared across many modules, so remember not to overwrite them in your code and extend the objects instead.
object | description |
---|---|
window.pos | Global namespace for all module-related data. |
window.pos.modules | If your module provides a public API or a constructor, then you should attach it to this object,t namespacing your module accordingly window.pos.module.myModule = {} |
window.pos.modules.active | If your module creates a separate instance and provides a public API, it can be attached to this object. |
window.pos.profile | Stores all the profile-related data for the currently logged-in user. |
window.pos.translations | If your JavaScript code needs access to any translations, you should append them to this object. |
As an example starting point for defining JavaScript for your module, you can use the following code:
<script>
/* namespace */
if(typeof window.pos !== 'object'){
window.pos = {};
window.pos.modules = {};
window.pos.translations = {};
}
/* profile namespace */
if(typeof window.pos.profile !== 'object'){
window.pos.profile = {};
}
window.pos.profile.myProfileVariable = 'foo';
/* translations used in module */
window.pos.translations = {
...window.pos.translations,
myTranslation: 'bar'
}
</script>
To enable debug mode, you can set the pos.debug
to true
in the JS Console. This will log events from the default provided modules.
When building your module, please use the following method to log debug data:
pos.modules.debug([true || module.settings.debug], [module id (string)], [message (string)]);
To provide a way of reacting to your module changes, please use JavaScript events when appropriate, prefixing the event with pos-
as follows:
document.dispatchEvent(new CustomEvent('pos-somethingHappenedInMyModule', { bubbles: true, detail: { something: 'new value' } }));
pos.modules.debug(module.settings.debug, 'event', `pos-somethingHappenedInMyModule`, { something: new value });
Using pos.modules.debug()
to add information about the event provides an easy way for the developers to react to changes provided by your module without the need to check the code or browse through documentation.
When using the import
statement in your JavaScript files, you will request a JS file from the CDN that could already be cached by the browser. platformOS handles breaking the cache for assets by using the asset_url
filter. You cannot use it in the JS files, though, but the browsers allow you to map any filename to any other URL using Import Maps. Currently, only a single import map on a page can be used, and it needs to be defined before any other JS script. (This will change soon as multiple import maps are in the works for all the browsers.)
An example import map looks like this:
<script type="importmap">
{
"imports": {
"/": "{{ 'modules/chat/js/' | asset_url }}",
"chat.js": "{{ 'modules/chat/js/chat.js' | asset_url }}",
"consumer.js": "{{ 'modules/chat/js/consumer.js' | asset_url }}",
"csrfToken.js": "{{ 'modules/chat/js/csrfToken.js' | asset_url }}",
"notifications.js": "{{ 'modules/chat/js/notifications.js' | asset_url }}",
"./": "./"
}
}
</script>
The first line allows you to use relative import
statements inside your JS files, the last line resets it back to the default.
- Render the partial in your application layout (preferably at the very bottom)
{% liquid
function flash = 'modules/core/commands/session/get', key: 'sflash'
if context.location.pathname != flash.from or flash.force_clear
function _ = 'modules/core/commands/session/clear', key: 'sflash'
endif
theme_render_rc 'modules/common-styling/toasts', params: flash
%}
From JavaScript, you can use:
new pos.modules.toast('[severity]', '[message]') to show new notification
On the server-side: [TO DO]
A pre-defined method of loading HTML content into a container:
const { load } = await import('modules/common-styling/js/pos-load.js');
pos.modules.load = load;
await pos.modules.load({
endpoint: [string],
target: [string],
method: [string],
trigger: [dom node],
triggerType: [string]
});
parameter | type | description |
---|---|---|
endpoint | string | URL of the endpoint that returns the HTML to be applied to a container |
target | string | Selector for the target container that the HTML will be applied to |
method | string | Either replace or append . The returned HTML will replace the content of the container or will be appended after the last node of the container |
trigger | dom node | The HTML element that triggers loading the endpoint |
triggerType | string | Either click or hover . Defines whether loading starts on click or hover trigger |
You can use the load
method directly or use the simpler method by adding some custom attributes to the trigger element that will initialize loading the endpoint when interacted with:
Clicking the following button will load the HTML from /test/example_endpoint
to the container with ID example_container
:
<button type="button" data-load-content="/test/example_endpoint" data-load-target="#example_container">
<div id="example_container">Loading…</div>
attribute | description |
---|---|
data-load-content | URL of the endpoint that returns the HTML to be applied to a container |
data-load-target | Selector for the target container that the HTML will be applied to |
data-load-method | replace or append – the returned HTML will either replace the container’s content or be appended after the last node of the container |
data-load-trigger-type | Defines whether the loading process is triggered by a click or mouseenter event |