7  jsPsych

learning goals
  • Understand what jsPsych is and how it functions as a JavaScript library for psychology experiments
  • Learn to properly load jsPsych, its plugins, and experiment files in HTML
  • Create basic experiments using jsPsych trial objects and the run() method
  • Navigate and utilize the official jsPsych documentation effectively
  • Configure trial behavior using general and plugin-specific parameters
  • Customize experiment appearance using CSS styling and jsPsych classes
  • Apply the standard jsPsych experiment structure: initialize, define trials, and run

7.1 Introduction

This week, we’ll harness our web development skills to craft behavioral experiments. However, there’s no need to start from scratch. jsPsych is a JavaScript library specifically designed for psychologists to achieve this very purpose.

7.1.1 What is a Library?

Before we dive into jsPsych, let’s understand what a “library” means in programming. Think of it like a real library, but instead of books, it contains pre-written code that solves common problems.

Imagine you wanted to bake a cake from scratch. You could:

  • Mill your own flour from wheat
  • Extract sugar from sugar cane
  • Churn your own butter
  • Raise chickens for eggs

Or, you could go to the grocery store and buy these ingredients that someone else has already prepared. A programming library works the same way in that it provides pre-made “ingredients” (functions and tools) that you can use in your code.

Without a library, creating a psychology experiment would require you to write all the JavaScript code needed to control the HTML and CSS. For example, if you wanted an image to suddenly appear on the display for 2 seconds, you’d need to write a function to show/hide the image HTML, then write more code to create a timer to trigger the show/hide behavior. If you wanted participants to respond to the image, you’d need to write functions that listened for keyboard inputs and recorded timestamps. You’d also need functions to store the data, code to loop through multiple images… This would quickly become overwhelming for even a simple experiment.

With a library like jsPsych, someone has already written all this code for you! You just need to learn how to use their pre-built tools.

Think of it this way:

  • HTML gave you building blocks (headings, paragraphs, buttons)
  • CSS gave you styling tools (colors, fonts, layouts)
  • JavaScript gave you programming logic (variables, functions, loops)
  • jsPsych gives you psychology experiment tools (trials, data collection, timing)

7.1.2 Loading JsPsych

jsPsych is organized into a base library containing core functionality and a series of ‘plugins’ that add specific features like displaying images or handling mouse inputs. This modular approach is useful because experiments only need certain functions—by using plugins, we only load what we need. For example, if your experiment uses only text stimuli, there’s no reason to load the image display plugin.

Let’s look at an example of loading jsPsych in our HTML page:

<!DOCTYPE html>
<html>
<head>
    <title>My experiment</title>
    <script src="jspsych/jspsych.js"></script>
    <link href="jspsych/jspsych.css" rel="stylesheet" type="text/css" />
</head>
<body></body>
</html>

Notice we loaded two files in our <head>:

  1. jspsych.js - The main JavaScript library stored in a jspsych folder
  2. jspsych.css - Basic styling that jsPsych provides (centering displays, font sizes, etc.)

This organization will be our standard approach for loading jsPsych. We’re loading the library locally from folders stored on our server. In your lab materials folder, you’ll find the same folder structure.

📂 L03
--  📄 index.html
--  📄 exp.js
--  📂 jspsych

This standardized organization means that once you understand how to load jsPsych in one lab, you’ll know how to do it in all the others. Each lab folder contains:

  • HTML file: The main webpage (usually index.html)
  • JavaScript file: Your experiment code (names vary: helloWorld.js, exp.js, etc.)
  • jsPsych folder: A complete copy of the jsPsych library with all necessary files

7.1.2.1 About the jsPsych Library

The jsPsych library included in your lab folders was downloaded from the official jsPsych website. While there are other ways to load jsPsych, we’re using local copies for several reasons:

  • Reliability: Your experiments will work even without an internet connection
  • Version consistency: Everyone uses the same version, avoiding compatibility issues
  • Learning clarity: You can see exactly which files are being loaded and where they’re located

We’ll explore the official jsPsych documentation later in this chapter, but for now, it’s sufficient to know that each lab folder contains everything you need to run jsPsych experiments. This self-contained approach makes it easy to understand the file structure and ensures your experiments will run consistently across different environments.

7.1.3 Loading jsPsych Plugins

Returning to our example, the previous HTML only loads the basic library without any plugins. For most experiments, we’ll need additional plugins. A common one is the keyboard response plugin, which enables participants to press keys while jsPsych records their responses.

Let’s add that plugin:

<!DOCTYPE html>
<html>
<head>
    <title>My experiment</title>
    <script src="jspsych/jspsych.js"></script>
    <script src="jspsych/plugin-html-keyboard-response.js"></script>
    <link href="jspsych/jspsych.css" rel="stylesheet" type="text/css" />
</head>
<body></body>
</html>

7.1.4 Loading the Experiment JavaScript

The final piece we need is a place to write our own JavaScript code to tell jsPsych what to do. We’ll load this in an external JavaScript file inside the <body>:

<!DOCTYPE html>
<html>
<head>
    <title>My experiment</title>
    <script src="jspsych/jspsych.js"></script>
    <script src="jspsych/plugin-html-keyboard-response.js"></script>
    <link href="jspsych/jspsych.css" rel="stylesheet" type="text/css" />
</head>
<body>
  <script src="helloWorld.js"></script>
</body>
</html>

Remember that files loaded in the <head> run before the page loads, while files inside the <body> run after the page loads. By putting our code in the body, we ensure it loads AFTER jsPsych and the basic page HTML have loaded. This is important because our code will depend on jsPsych being available.

7.2 Example: Hello World

Now let’s see a complete example in action. This is the 01_helloWorld example you’ll find in your demos folder. In the “Result” tab you’ll find a live version of Hello World. In this demo, the text “Hello World!” is displayed and when you press any key on your keyboard, it disappears. Note that for these embedded demos, you’ll need to click on the frame to be able to provide any input.

Go ahead and try it out! You can click the refresh button to reload the page and do it again if you’d like.

<!DOCTYPE html>
<html>
<head>
    <title>My experiment</title>
    <script src="jspsych/jspsych.js"></script>
    <script src="jspsych/plugin-html-keyboard-response.js"></script>
    <link href="jspsych/jspsych.css" rel="stylesheet" type="text/css" />
</head>
<body>
<script src="helloWorld.js"></script>
</body>
</html>
const jsPsych = initJsPsych();

const hello_trial = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: 'Hello world!'
}

jsPsych.run([hello_trial]);
Live JsPsych Demo Click inside the demo to activate keyboard input

7.2.1 Breaking Down the JavaScript Code

Let’s examine what each line of JavaScript does in our helloWorld.js file:

const jsPsych = initJsPsych();

const hello_trial = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: 'Hello world!'
}

jsPsych.run([hello_trial]);

7.2.1.1 Line 1: Initialize jsPsych

const jsPsych = initJsPsych();

This line creates our jsPsych instance. Think of initJsPsych() as starting up the jsPsych “engine” that will control our experiment. We store this in a constant called jsPsych that we’ll use throughout our code.

The initJsPsych() function:

  • Sets up the internal systems for timing, data collection, and display management
  • Prepares the HTML page for experiment content
  • Returns an object with methods we can use to control the experiment

This will always be the first thing you need to do in your code.

7.2.1.2 Lines 3-6: Define a Trial

const hello_trial = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: 'Hello world!'
}

Here we’re creating a trial object. A ‘trial’ is the fundamental building block of jsPsych experiments. Every trial is a JavaScript object that describes what should happen during that part of the experiment. In jsPsych, they use the word ‘trial’ to refer to any event that could occur in the experiment. I tend to think of them as ‘events’ rather than ‘trials’ because, as we’ll see in the next example, we can present instructions, or anything at all, using the trial object.

This trial object has two properties:

  • type: Specifies which jsPsych plugin to use. jsPsychHtmlKeyboardResponse tells jsPsych to use the HTML keyboard response plugin we loaded earlier. This plugin displays HTML content and waits for a keyboard response. The type always follows the same format: it is the plugin name but converted from the hyphenated name version to camel case (ie., no spaces and capital letters) with jsPsych at the beginning. E.g., html-keyboard-response = jsPsychHtmlKeyboardResponse
  • stimulus: The content to display to the participant. In this case, it’s the text “Hello world!”

When this trial runs, jsPsych will:

  1. Display “Hello world!” on the screen
  2. Wait for the participant to press any key
  3. Record the response time and which key was pressed
  4. Move to the next trial (or end the experiment if this is the last trial)

7.2.1.3 Line 8: Run the Experiment

jsPsych.run([hello_trial]);

This line starts the experiment. The jsPsych.run() method takes an array of trials as its argument. Even though we only have one trial, we still need to put it in an array (notice the square brackets []).

When run() is called, jsPsych:

  1. Takes control of the webpage
  2. Executes each trial in the array sequentially
  3. Handles all the timing, display, and data collection automatically
  4. Stores the results for later retrieval

This will always be the final line in your code because it is the one that triggers the start of the experiment.

We now have a general template for how all of our experiments will be programmed:

// 1. Initialize jsPsych


// 2. Define our trials/events


// 3. Run jsPsych with our trials

7.3 Example: The Shortest Experiment

Let’s look at one more example. This one will have a few more trials, but follows our general template.

<!DOCTYPE html>
<html>
<head>
    <title>My experiment</title>
    <script src="jspsych/jspsych.js"></script>
    <script src="jspsych/plugin-html-keyboard-response.js"></script>
    <link href="jspsych/jspsych.css" rel="stylesheet" type="text/css" />
</head>
<body>
<script src="exp.js"></script>
</body>
</html>
// 1. Initialize jsPsych
const jsPsych = initJsPsych();

// 2. Define our trials
const instructions = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: 'This is a test of your reflexes. When the experiment begins, you will see a letter on the screen. Your task is to press that letter on your keyboard as quickly as possible. When you are ready, press any key on the keyboard to begin.'
}

const letter = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: 'H'
}

const debrief = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: 'Great job! You just completed the shortest experiment!'
}

// 3. Run jsPsych with our trials
jsPsych.run([
  instructions,
  letter,
  debrief
]);
Live JsPsych Demo Click inside the demo to activate keyboard input

7.4 JsPsych Documentation

The official jsPsych documentation is your most valuable resource for learning about the library’s capabilities. You can find it at https://www.jspsych.org/v8/. The documentation is well-organized and includes everything from basic tutorials to advanced features.

7.4.1 Using the Correct Version

Important: Always make sure you’re viewing the documentation for the correct version of jsPsych. The labs in this course use jsPsych version 8, but the documentation site defaults to the latest version (which may be newer).

To ensure you’re viewing the v8 documentation:

  1. Go to https://www.jspsych.org
  2. Look for the version dropdown at the top of the page
  3. Select “v8” from the dropdown menu
  4. The URL should change to https://www.jspsych.org/v8/

Using the wrong version’s documentation can lead to confusion, as newer versions may have different parameter names, new features, or changed syntax that won’t work with the version installed in your lab materials.

7.4.2 Key Documentation Sections

The documentation is structured into several main areas:

  • Overview: Introduction to jsPsych concepts and getting started guides
  • Tutorials: Step-by-step walkthroughs for common experiment types
  • Plugins: Detailed reference for each available plugin
  • Extensions: Additional functionality that can be added to experiments
  • API Reference: Complete technical documentation of all jsPsych functions

7.4.3 Using Plugin Documentation

Each plugin has its own documentation page that follows a consistent structure:

  1. Description: What the plugin does and when to use it
  2. Parameters: All available options you can configure
  3. Data Generated: What information the plugin records
  4. Examples: Working code samples
  5. Simulation Mode: How the plugin behaves during testing

For example, the HTML Keyboard Response plugin documentation shows you can control things like:

  • Which keys are valid responses
  • How long to wait for a response
  • Whether to show a prompt
  • Custom CSS styling

7.4.4 Finding What You Need

When working on an experiment, you’ll typically:

  1. Browse plugins to find the right tool for your task (image display, audio playback, surveys, etc.)
  2. Check parameters to see what options are available
  3. Review examples to understand the syntax
  4. Look at data output to plan your analysis

The search function on the documentation site is particularly helpful for finding specific features or troubleshooting issues.

7.5 Trial Parameters

Every jsPsych trial is defined by parameters that control its behavior. Understanding these parameters is crucial for creating effective experiments.

7.5.1 General Parameters

In the previous examples, when we created our trial object, we added a couple of options called “parameters”. These parameters define what happens in our trial according to the jsPsych documentation. There are general parameters that are available to any trial we create and there are plugin-specific parameters that are only available to certain types. The type parameter is always required, because this is the parameter that tells jsPsych what kind of trial it is, and which plugin should be used.

First, let’s review the general parameters. I’ve recreated the table from the documentation, you’d normally find here: https://www.jspsych.org/latest/overview/plugins/#parameters-available-in-all-plugins

Parameter Type Default Value Description
data object undefined An object containing additional data to store for the trial. See the Data page for more details.
post_trial_gap numeric null Sets the time, in milliseconds, between the current trial and the next trial. If null, there will be no gap.
on_start function function(){ return; } A callback function to execute when the trial begins, before any loading has occurred. See the Event-Related Callbacks page for more details.
on_finish function function(){ return; } A callback function to execute when the trial finishes, and before the next trial begins. See the Event-Related Callbacks page for more details.
on_load function function(){ return; } A callback function to execute when the trial has loaded, which typically happens after the initial display of the plugin has loaded. See the Event-Related Callbacks page for more details.
css_classes string null A list of CSS classes to add to the jsPsych display element for the duration of this trial. This allows you to create custom formatting rules (CSS classes) that are only applied to specific trials.
save_trial_parameters object {} An object containing any trial parameters that should or should not be saved to the trial data. Each key is the name of a trial parameter, and its value should be true or false, depending on whether or not its value should be saved to the data.
save_timeline_variables boolean or array false If set to true, then all timeline variables will have their current value recorded to the data for this trial. If set to an array, then any variables listed in the array will be saved.
record_data boolean true If set to false, then the data for this trial will not be recorded.

7.5.1.1 Column 1: Parameter

This column shows the name of the parameter which is exactly what you would type in your trial object. These are the property names you can add to any trial, regardless of which plugin you’re using.

For example, if you see data in this column, you would use it like this:

const trial = {   
  type: jsPsychHtmlKeyboardResponse,   
  stimulus: 'Hello',   
  data: { 
    condition: 'practice' 
    }  
  } 

7.5.1.2 Column 2: Type

This tells you what kind of value the parameter expects. Understanding data types is crucial for writing correct code:

  • object: A JavaScript object with curly braces {} containing key-value pairs
  • numeric: A number (like 1000 for milliseconds)
  • function: A JavaScript function that will be executed at a specific time
  • string: Text enclosed in quotes
  • boolean: Either true or false
  • array: A list of items in square brackets []
data: { trial_type: 'test' },         // object
post_trial_gap: 2000,                 // numeric  
on_start: function() { ... },         // function
css_classes: 'my-custom-class',       // string
record_data: false,                   // boolean
save_timeline_variables: ['condition'] // array

7.5.1.3 Column 3: Default Value

This shows what happens if you don’t specify the parameter. jsPsych will use this default value automatically.

  • undefined: The parameter won’t exist unless you add it
  • null: The parameter exists but has no value (often means “disabled”)
  • false: The feature is turned off by default
  • {}: An empty object
  • function(){ return; }: A function that does nothing

Understanding defaults helps you know which parameters are optional (most of them), what the ‘normal’ behaviour is, and when you need to override them.

For example, record_data defaults to true, so trials are normally saved. You only need to specify record_data: false if you want to prevent saving.

7.5.1.4 Column 4: Description

This explains what the parameter does and when you might use it. This is where you learn the practical purpose of each parameter.

Key things to look for in descriptions:

  • When the parameter takes effect (trial start, trial end, etc.)
  • What it controls or modifies
  • References to other documentation pages for more details
  • Special behaviors or limitations

7.5.1.5 Putting It All Together

Let’s look at one row as a complete example:

Parameter Type Default Value Description
post_trial_gap numeric null Sets the time, in milliseconds, between the current trial and the next trial. If null, there will be no gap.

This tells us:

  • Name: Use post_trial_gap in your trial object
  • Type: Expects a number (milliseconds)
  • Default: null means no gap between trials normally
  • Purpose: Controls timing between trials

So you could use it like:

const trial = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: 'Press any key',
  post_trial_gap: 1500  // 1.5 second pause after this trial
}

7.5.2 Plugin Parameters

In addition to the Each plugin defines its own specific parameters. Let’s examine the HTML Keyboard Response plugin as an example. You’d find this table here: https://www.jspsych.org/latest/plugins/html-keyboard-response/.

Parameter Type Default Value Description
stimulus HTML string undefined The string to be displayed.
choices array of strings “ALL_KEYS” This array contains the key(s) that the participant is allowed to press in order to respond to the stimulus. Keys should be specified as characters (e.g., ‘a’, ‘q’, ’ ‘, ’Enter’, ‘ArrowDown’) - see this page and this page (event.key column) for more examples. Any key presses that are not listed in the array will be ignored. The default value of “ALL_KEYS” means that all keys will be accepted as valid responses. Specifying “NO_KEYS” will mean that no responses are allowed.
prompt string null This string can contain HTML markup. Any content here will be displayed below the stimulus. The intention is that it can be used to provide a reminder about the action the participant is supposed to take (e.g., which key to press).
stimulus_duration numeric null How long to display the stimulus in milliseconds. The visibility CSS property of the stimulus will be set to hidden after this time has elapsed. If this is null, then the stimulus will remain visible until the trial ends.
trial_duration numeric null How long to wait for the participant to make a response before ending the trial in milliseconds. If the participant fails to make a response before this timer is reached, the participant’s response will be recorded as null for the trial and the trial will end. If the value of this parameter is null, then the trial will wait for a response indefinitely.
response_ends_trial boolean true If true, then the trial will end whenever the participant makes a response (assuming they make their response before the cutoff specified by the trial_duration parameter). If false, then the trial will continue until the value for trial_duration is reached. You can set this parameter to false to force the participant to view a stimulus for a fixed amount of time, even if they respond before the time is complete.

This table follows the same interpretation of the General Parameters table. However, it’s important to note that any parameter that has a ‘Default Value’ of undefined is required. That means, for the HTML keyboard response plugin, we must specify a stimulus, otherwise our code will fail to run and we will receive an error.

All other parameters are optional and, if you don’t need to change the default, then you don’t need to refer to them at all.

For example, I could run either of these two:

const trial_a = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: 'BLUE'
}

const trial_b = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: 'BLUE',
  prompt: '<p>Press the first letter of this word</p>'
}

Trial A will not have any prompt displayed below the trial stimulus and Trial B will have the prompt printed below the trial stimulus.

And finally, to provide a full example, using all the HTML keyboard response parameters:

const complex_trial = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: '<div style="font-size: 48px; color: blue;">TARGET</div>',
  choices: ['f', 'j'],
  prompt: '<p>Press F for blue, J for red</p>',
  stimulus_duration: 2000,
  trial_duration: 5000,
  response_ends_trial: false
}

In this example, the trial:

-Shows a large blue “TARGET” -Only accepts ‘f’ or ‘j’ key presses -Displays “Press F for blue, J for red” below the target stimulus -Displays the target stimulus for 2 seconds (and then removes it after 2 seconds) -Times out after 5 seconds and automatically proceeds to the next trial -Prevents the response from ending the trial

7.6 Styling

jsPsych provides default styling that works well for most experiments, but you can customize the appearance to match your research needs or institutional branding. Default jsPsych Styling

The jspsych.css file provides: - Centered content display - Consistent fonts and spacing - Responsive design for different screen sizes - Basic button and form styling

jsPsych automatically applies specific CSS classes to different parts of your experiment. Understanding these classes is key to effective styling because they represent the different components of the jsPsych interface.

7.6.1 Core Structure Classes

7.6.1.1 .jspsych-display-element

This is the main container for your entire experiment. It wraps all content and is where you’d set global styles like background colors, fonts, or overall layout properties.

7.6.1.2 .jspsych-content

This contains the actual content of each trial (the stimulus, buttons, etc.). It’s centered within the display element and is where you’d control the maximum width, padding, or alignment of your experiment content.

7.6.1.3 .jspsych-btn / .jspsych-btn:hover

Applied to all buttons created by jsPsych plugins (like multiple choice responses). This is where you’d style button appearance, hover effects, and spacing.

7.6.2 Customizing the Display

You can add your own CSS rules to modify the appearance. We can also rewrite these jsPsych classes if we want to change the overall look of our experiment. In our style.css file, for example, we could re-write the classes, and if this file is loaded after the jspsych.css file, it would override any styling already applied. Here’s an example:

.jspsych-display-element {
  background-color: #f8f9fa;
  font-family: 'Arial', sans-serif;
  color: #333;
}

.jspsych-content {
  max-width: 800px;
  margin: auto;
  padding: 20px;
  background-color: white;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}

.jspsych-btn {
  background-color: #007bff;
  color: white;
  border: none;
  padding: 12px 24px;
  margin: 8px;
  border-radius: 6px;
  font-size: 16px;
  cursor: pointer;
  transition: background-color 0.2s;
}

/* Buttons change their style when you hover over them. Here, the background-color changes on hover */
.jspsych-btn:hover {
  background-color: #0056b3;
}

/* Buttons that are temporarily disabled get this state. You can style disabled buttons differently to provide visual feedback */
.jspsych-btn:disabled {
  background-color: #6c757d;
  cursor: not-allowed;
  opacity: 0.6;
}

7.6.3 Creating a Dark Theme

Many experiments benefit from a dark theme, especially those involving visual stimuli or long sessions where eye strain is a concern. The default jsPsych styling uses black text on a white background, which can cause eye fatigue during extended experimental sessions. However, we should avoid simply inverting to pure white text on a black background, as this creates harsh contrast that can be equally uncomfortable.

A better approach is to use light grey text on a dark background, which provides excellent readability while being much gentler on the eyes. We can achieve this by overwriting the default CSS, targeting both standard HTML tags (like h1 or p) and jsPsych’s specific CSS classes.

Here’s how we would do that by writing CSS in an external file and linking to it in our HTML:

<!DOCTYPE html>
<html>
<head>
    <title>Lexical Decision</title>
    <!-- jsPsych -->
    <script src="jspsych/jspsych.js"></script>
    <link href="jspsych/jspsych.css" rel="stylesheet" type="text/css" />
    
    <!-- jPsych plugins -->
    <script src="jspsych/plugin-html-keyboard-response.js"></script>

    <!-- custom CSS -->
    <link href="style.css" rel="stylesheet" type="text/css" />
</head>
<body>
  <!-- custom JS -->
  <script src="exp.js"></script>
</body>
</html>
// 1. Initialize jsPsych
const jsPsych = initJsPsych();

// 2. Define our trials
const welcome = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: `<h1>Dark Theme Experiment</h1>
             <p>This experiment uses a dark theme with light grey text to reduce eye strain.</p>
             <p>Press any key to continue.</p>`,
  choices: 'ALL_KEYS',
  post_trial_gap: 500
}

const instructions = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: `<h2>Instructions</h2>
             <p>You'll be shown a series of words and asked to respond.</p>
             <p>Notice how the dark background is easier on your eyes during longer sessions.</p>
             <p>Press any key to begin.</p>`,
  choices: 'ALL_KEYS',
  post_trial_gap: 500
}

const word_trial_1 = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: `<div style='font-size: 48px; font-weight: bold;'>
               pulchritudinous
             </div>
             <p>Press 'R' if this is a real word, 'F' if it's fake</p>`,
  choices: ['r', 'f'],
  post_trial_gap: 250
}

const word_trial_2 = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: `<div style='font-size: 48px; font-weight: bold;'>
               psychotomimetic
             </div>
             <p>Press 'R' if this is a real word, 'F' if it's fake</p>`,
  choices: ['r', 'f'],
  post_trial_gap: 250
}

const button_trial = {
  type: jsPsychHtmlButtonResponse,
  stimulus: `<h3>How comfortable was the dark theme?</h3>`,
  button_layout: 'grid',
  grid_rows: 3,
  choices: ['Great!', 'Ok', 'Terrible!'],
  post_trial_gap: 500
}

const debrief = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: `<h2>Thank You!</h2>
             <p>You've completed the dark theme experiment.</p>
             <p>Notice how the light grey text (#e0e0e0) on dark background (#1a1a1a) provides good contrast without being harsh.</p>
             <p>This is much more comfortable than pure white on black.</p>`,
  choices: "NO_KEYS"
}

// 3. Run jsPsych with our trials
jsPsych.run([
  welcome,
  instructions,
  word_trial,
  button_trial,
  debrief
]);

/* Headings with subtle color accents */
h1 {
  color: #4a9eff; /* Soft blue */
  text-align: center;
  font-size: 32px;
}

h2 {
  color: #66d9ef; /* Soft cyan */
  text-align: center;
}

h3 {
  color: #a6e22e; /* Soft green */
  text-align: center;
  margin-bottom: 20px;
  font-size: 24px;
}

/* Paragraph styling */
p {
  font-size: 18px;
  line-height: 1.6;
  text-align: center;
  color: #e0e0e0;
}

/* Main container - dark background */
.jspsych-display-element {
  background-color: #1a1a1a;
  color: #e0e0e0; /* Light grey instead of pure white */
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;

}

/* Content area */
.jspsych-content {
  color: #e0e0e0;
  max-width: 500px;
}


/* Button styling for dark theme */
.jspsych-btn {
  background-color: #404040;
  color: #e0e0e0;
  border: 1px solid #606060;
  padding: 12px 24px;
  margin: 8px;
  border-radius: 6px;
  font-size: 16px;
  cursor: pointer;
  transition: all 0.2s ease;
}

.jspsych-btn:hover {
  background-color: #505050;
  border-color: #4a9eff;
  color: #ffffff;
}

.jspsych-btn:disabled {
  background-color: #2a2a2a;
  color: #808080;
  border-color: #404040;
  cursor: not-allowed;
  opacity: 0.6;
}
Live JsPsych Demo Click inside the demo to activate keyboard input