Tutorials

How to Build and Render Your First Joystick Component

Learn how to create your first Joystick component from scratch, including HTML rendering, CSS styling, and basic interactivity.

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 a count property set to 0
  • Destructured state from the component instance in the render() 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:

  1. Component Structure: How to use joystick.component() with a render() function
  2. CSS Styling: How to add scoped CSS to your components
  3. State Management: How to define initial state and update it with set_state()
  4. Event Handling: How to handle DOM events using the events object
  5. Component Composition: How to use components inside other components
  6. Props: How to make components flexible with default_props and props

Next Steps

Now that you understand the basics, you can:

Your component demonstrates all the core concepts of Joystick's component system. From here, you can build increasingly complex and interactive user interfaces!