Storylang is a language that can be used for storyboarding user experiences. It brings precision and clarity to the collaborative process for people involved in UX design and frontend development.
Storylang is JSON-compatible.
Long-term goal: Storylang must evolve into a written script notation framework that can help imagine, annotate and communicate.
Inspiration to create this was drawn from music notations.
Above: Hand-written musical notation by J. S. Bach (1685–1750).
Storylang.json is a structured configuration file used in the ember-tribe ecosystem to define the frontend implementation of your application. It works in conjunction with your simplified-types.json (which defines your data models) and navigator.json (which defines user flows and navigation) to create a complete frontend specification.
The storylang.json file serves as a blueprint for frontend developers to understand:
- What routes, components and services are required
- How data flows through the application
The storylang.json file contains four main sections:
{
"implementation_approach": "...",
"components": [...],
"routes": [...],
"services": [...]
}Purpose: Provides a high-level technical overview of how the frontend interface would work.
Format:
{
"implementation_approach": "Two-paragraph description explaining technical approach and key functionality."
}Purpose: Defines reusable UI components that will be built for the application.
Format:
{
"components": [
{
"name": "component-name",
"type": "component-type",
"tracked_vars": [{ "variable_name": "data_type" }],
"inherited_args": [{ "arg_name": "type" }],
"actions": ["action1", "action2"],
"helpers": ["helper1", "helper2"],
"modifiers": ["modifier1"],
"services": ["service1", "service2"]
}
]
}Component Properties:
name: Kebab-case name of the componenttype: Type from the built-in components listtracked_vars: State variables tracked within the componentinherited_args: Arguments passed from parent componentsactions: User interactions the component handleshelpers: Template helpers used within the componentmodifiers: DOM modifiers applied to elementsservices: Ember services injected into the component
Built-in Component Types:
- Layout:
table,figure,accordion,card,list-group,navbar,nav,tab,breadcrumb - Interactive:
button,button-group,dropdown,modal,collapse,offcanvas,pagination,popover,tooltip,swiper-carousel,videojs-player,howlerjs-player,input-field,input-group,textarea,checkbox,radio,range,select,multi-select,date,file-uploader,alert,badge,toast,placeholder,progress,spinner,scrollspy
Example:
{
"components": [
{
"name": "file-summary-card",
"type": "card",
"tracked_vars": [{ "isSelected": "bool" }, { "isExpanded": "bool" }],
"inherited_args": [
{ "file": "var" },
{ "onEdit": "fn" },
{ "onDelete": "fn" }
],
"actions": ["toggleSelection", "expandDetails", "editFile", "deleteFile"],
"helpers": ["formatDate", "truncateText"],
"modifiers": ["tooltip"],
"services": ["store", "router"]
}
]
}Purpose: Defines the application's routes and their requirements.
Format:
{
"routes": [
{
"name": "route-name",
"tracked_vars": [{ "variable_name": "data_type" }],
"get_vars": [{ "param_name": "data_type" }],
"actions": ["action1", "action2"],
"helpers": ["helper1"],
"services": ["service1"],
"components": ["component1", "component2"],
"models": ["model1", "model2"]
}
]
}Route Properties:
name: The route name (matches Ember router)tracked_vars: Route-level state variablesget_vars: URL query parameters and their typesactions: Route-level actionshelpers: Template helpers used in route templatesservices: Services used by the routecomponents: Components rendered in this routemodels: Data models loaded by this route
Example:
{
"routes": [
{
"name": "files",
"tracked_vars": [
{ "selectedFileType": "string" },
{ "sortOrder": "string" }
],
"get_vars": [
{ "page": "int" },
{ "search": "string" },
{ "filter": "string" }
],
"actions": ["filterFiles", "sortFiles", "searchFiles"],
"helpers": ["pluralize", "formatDate"],
"services": ["store", "router"],
"components": ["file-list", "file-filter", "search-box", "pagination"],
"models": ["json_file"]
}
]
}Purpose: Defines custom Ember services needed by the application.
Format:
{
"services": [
{
"name": "service-name",
"tracked_vars": [{ "variable_name": "data_type" }],
"actions": ["action1", "action2"],
"helpers": ["helper1"],
"services": ["dependency1", "dependency2"]
}
]
}Service Properties:
name: Service nametracked_vars: Service-level tracked propertiesactions: Methods provided by the servicehelpers: Utility functionsservices: Other services this service depends on
Built-in Services:
store: Ember Data store for CRUD operationsrouter: Ember router service for navigation
Third-party npm packages pre-installed:
bootstrap: Bootstrap CSS framework with Popper.jspapaparse: CSV parsing librarysortablejs: Drag-and-drop sortingswiperjs: Touch slider/carouselvideojs: Video playerhowlerjs: Audio player
Example:
{
"services": [
{
"name": "visualization-builder",
"tracked_vars": [
{ "currentVisualization": "object" },
{ "availableTypes": "array" }
],
"actions": [
"createVisualization",
"updateVisualization",
"deleteVisualization"
],
"helpers": ["validateConfig", "generatePreview"],
"services": ["store", "router"]
}
]
}string: Text valuesint: Integer numbersbool: Boolean true/falsearray: List of itemsobject: Complex data structurevar: Variable (any type)fn: Function reference
var: Passed data/statefn: Callback functionaction: User interaction handler
- Model names used in routes should match type names from simplified-types.json
- Component inherited_args often reference model data pulled from store, and/or their fields
- Models are the gateway to persistent storage on the backend.
Always begin your thought process with routes → then move repeatable template route into components → then move your repeatable app-wide logic from components to services
- Start with Routes: Match route names to user mental models and use consistent naming conventions
- Minimize Route Logic: Preferably fetch (read) model data in routes and then pass that data to components or services. Other than fetching model data, minimize the use of javascript in routes - Javascript is meant to be in components and services more than in routes
- Route Parameters: Keep get_vars minimal and meaningful, and load only necessary models for each route
- Component Focus: Keep components focused on single responsibilities and use descriptive, kebab-case names
- Data Flow: Receive backend data down from routes (via inherited_args) rather than fetching (reading) in components
- Component Actions: Non-read functions - create, update, delete - can all happen well at component-level
- Service Integration: Use services directly in components for app-wide logic
- Service Architecture: Keep services stateless when possible and use dependency injection for service composition
- Service Role: Services interact with both routes and components and store the core logic of the application
- Start with implementation_approach: Write a clear use case first
- Identify key user journeys: Map these to routes
- Break down UI into components: Focus on reusability
- Define data flow: Ensure models align with your simplified-types.json
- Plan service architecture: Keep core application logic separate from UI logic
- Validate interconnections: Ensure all references match between files
- Test component composition: Verify components work together logically
