This tutorial was written by AI
The examples and explanations have been reviewed for accuracy. If you find an error, please let us know.
In this tutorial, you'll learn how to build and render your first Joystick component. We'll create a simple but functional component that demonstrates the core concepts of Joystick's component system.
What You'll Build
By the end of this tutorial, you'll have created a "Welcome Card" component that:
- Renders HTML content
- Includes scoped CSS styling
- Handles user interactions with DOM events
- Manages component state
Prerequisites
Before starting, make sure you have:
- A Joystick app created and running (see Creating an App)
- Basic knowledge of HTML, CSS, and JavaScript
Step 1: Create the Component File
First, let's create a new component file. In Joystick, components should be placed in the ui/components
directory following this structure:
ui/components/<component_name>/index.js
Create a new directory and file:
ui/components/welcome_card/index.js
import joystick from '@joystick.js/ui';
const WelcomeCard = joystick.component({
render: () => {
return `
<div class="welcome-card">
<h2>Welcome to Joystick!</h2>
<p>This is your first component.</p>
</div>
`;
},
});
export default WelcomeCard;
This is the most basic Joystick component. It uses the joystick.component()
method with a render()
function that returns HTML as a template literal string.
Step 2: Add CSS Styling
Let's add some styling to make our component look better. Joystick automatically scopes CSS to each component:
ui/components/welcome_card/index.js
import joystick from '@joystick.js/ui';
const WelcomeCard = joystick.component({
css: `
.welcome-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 2rem;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
text-align: center;
max-width: 400px;
margin: 2rem auto;
}
.welcome-card h2 {
margin: 0 0 1rem 0;
font-size: 1.5rem;
font-weight: 600;
}
.welcome-card p {
margin: 0;
opacity: 0.9;
line-height: 1.5;
}
`,
render: () => {
return `
<div class="welcome-card">
<h2>Welcome to Joystick!</h2>
<p>This is your first component.</p>
</div>
`;
},
});
export default WelcomeCard;
Step 3: Add Component State
Now let's add some state to make our component interactive. We'll add a counter that users can increment:
ui/components/welcome_card/index.js
import joystick from '@joystick.js/ui';
const WelcomeCard = joystick.component({
state: {
count: 0,
},
css: `
.welcome-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 2rem;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
text-align: center;
max-width: 400px;
margin: 2rem auto;
}
.welcome-card h2 {
margin: 0 0 1rem 0;
font-size: 1.5rem;
font-weight: 600;
}
.welcome-card p {
margin: 0 0 1rem 0;
opacity: 0.9;
line-height: 1.5;
}
.counter {
background: rgba(255, 255, 255, 0.2);
padding: 1rem;
border-radius: 8px;
margin-top: 1rem;
}
.counter-display {
font-size: 2rem;
font-weight: bold;
margin-bottom: 0.5rem;
}
.counter button {
background: rgba(255, 255, 255, 0.3);
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 6px;
cursor: pointer;
font-size: 1rem;
transition: background 0.2s;
}
.counter button:hover {
background: rgba(255, 255, 255, 0.4);
}
`,
render: ({ state }) => {
return `
<div class="welcome-card">
<h2>Welcome to Joystick!</h2>
<p>This is your first component.</p>
<div class="counter">
<div class="counter-display">${state.count}</div>
<button class="increment-btn">Click me!</button>
</div>
</div>
`;
},
});
export default WelcomeCard;
Notice how we:
- Added a
state
object with acount
property set to0
- Destructured
state
from the component instance in therender()
function - Used
${state.count}
to display the current count value
Step 4: Add Event Handling
Now let's add an event handler to make the button functional:
ui/components/welcome_card/index.js
import joystick from '@joystick.js/ui';
const WelcomeCard = joystick.component({
state: {
count: 0,
},
events: {
'click .increment-btn': (event, instance) => {
instance.set_state({
count: instance.state.count + 1,
});
},
},
css: `
.welcome-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 2rem;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
text-align: center;
max-width: 400px;
margin: 2rem auto;
}
.welcome-card h2 {
margin: 0 0 1rem 0;
font-size: 1.5rem;
font-weight: 600;
}
.welcome-card p {
margin: 0 0 1rem 0;
opacity: 0.9;
line-height: 1.5;
}
.counter {
background: rgba(255, 255, 255, 0.2);
padding: 1rem;
border-radius: 8px;
margin-top: 1rem;
}
.counter-display {
font-size: 2rem;
font-weight: bold;
margin-bottom: 0.5rem;
}
.counter button {
background: rgba(255, 255, 255, 0.3);
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 6px;
cursor: pointer;
font-size: 1rem;
transition: background 0.2s;
}
.counter button:hover {
background: rgba(255, 255, 255, 0.4);
}
`,
render: ({ state }) => {
return `
<div class="welcome-card">
<h2>Welcome to Joystick!</h2>
<p>This is your first component.</p>
<div class="counter">
<div class="counter-display">${state.count}</div>
<button class="increment-btn">Click me!</button>
</div>
</div>
`;
},
});
export default WelcomeCard;
The events
object uses the pattern '<event> <selector>'
as keys. When the button with class .increment-btn
is clicked, the handler function runs and updates the state using instance.set_state()
.
Step 5: Use the Component in a Page
Now let's use our component in a page. Open your main index page:
ui/pages/index/index.js
import joystick from '@joystick.js/ui';
import WelcomeCard from '../../components/welcome_card/index.js';
const Index = joystick.component({
render: ({ component }) => {
return `
<div class="index">
<h1>My Joystick App</h1>
${component(WelcomeCard)}
</div>
`;
},
});
export default Index;
The component()
render method allows you to embed one component inside another. We import our WelcomeCard
component and render it using ${component(WelcomeCard)}
.
Step 6: Add Props Support
Let's make our component more flexible by adding props support:
ui/components/welcome_card/index.js
import joystick from '@joystick.js/ui';
const WelcomeCard = joystick.component({
default_props: {
title: 'Welcome to Joystick!',
message: 'This is your first component.',
initialCount: 0,
},
state: (instance) => ({
count: instance.props.initialCount || 0,
}),
events: {
'click .increment-btn': (event, instance) => {
instance.set_state({
count: instance.state.count + 1,
});
},
},
css: `
.welcome-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 2rem;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
text-align: center;
max-width: 400px;
margin: 2rem auto;
}
.welcome-card h2 {
margin: 0 0 1rem 0;
font-size: 1.5rem;
font-weight: 600;
}
.welcome-card p {
margin: 0 0 1rem 0;
opacity: 0.9;
line-height: 1.5;
}
.counter {
background: rgba(255, 255, 255, 0.2);
padding: 1rem;
border-radius: 8px;
margin-top: 1rem;
}
.counter-display {
font-size: 2rem;
font-weight: bold;
margin-bottom: 0.5rem;
}
.counter button {
background: rgba(255, 255, 255, 0.3);
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 6px;
cursor: pointer;
font-size: 1rem;
transition: background 0.2s;
}
.counter button:hover {
background: rgba(255, 255, 255, 0.4);
}
`,
render: ({ props, state }) => {
return `
<div class="welcome-card">
<h2>${props.title}</h2>
<p>${props.message}</p>
<div class="counter">
<div class="counter-display">${state.count}</div>
<button class="increment-btn">Click me!</button>
</div>
</div>
`;
},
});
export default WelcomeCard;
Now you can pass props to customize the component:
ui/pages/index/index.js
import joystick from '@joystick.js/ui';
import WelcomeCard from '../../components/welcome_card/index.js';
const Index = joystick.component({
render: ({ component }) => {
return `
<div class="index">
<h1>My Joystick App</h1>
${component(WelcomeCard, {
title: 'Hello, Developer!',
message: 'You\'ve successfully created your first Joystick component.',
initialCount: 5,
})}
</div>
`;
},
});
export default Index;
Step 7: Test Your Component
Start your Joystick app if it's not already running:
joystick start
Visit http://localhost:2600
in your browser. You should see your welcome card component with:
- Custom title and message
- A counter starting at 5
- A clickable button that increments the counter
What You've Learned
Congratulations! You've built your first Joystick component and learned:
- Component Structure: How to use
joystick.component()
with arender()
function - CSS Styling: How to add scoped CSS to your components
- State Management: How to define initial state and update it with
set_state()
- Event Handling: How to handle DOM events using the
events
object - Component Composition: How to use components inside other components
- Props: How to make components flexible with
default_props
and props
Next Steps
Now that you understand the basics, you can:
- Add more complex interactions and animations
- Learn about fetching data in components
- Explore component lifecycle methods
- Build more sophisticated UIs by composing multiple components
Your component demonstrates all the core concepts of Joystick's component system. From here, you can build increasingly complex and interactive user interfaces!