Build interactive web applications using HTML/JavaScript interfaces with MATLAB computational backends via the uihtml component. Use when creating HTML-based MATLAB apps, JavaScript MATLAB interfaces, web UIs with MATLAB, interactive MATLAB GUIs, or when user mentions uihtml, HTML, JavaScript, web apps, or web interfaces.
This skill covers how to build interactive web applications that combine HTML/JavaScript interfaces with MATLAB computational backends using the uihtml component. The HTML side handles the UI; MATLAB does the computation.
The uihtml component enables bidirectional communication between JavaScript and MATLAB through several mechanisms:
Use Case: Sending data from MATLAB to update the HTML interface
% MATLAB side
h.Data = "Hello World!";
// JavaScript side
htmlComponent.addEventListener("DataChanged", function(event) {
document.getElementById("display").innerHTML = htmlComponent.Data;
});
Use Case: Triggering MATLAB functions from user interactions
// JavaScript side - send event to MATLAB
htmlComponent.sendEventToMATLAB("Calculate", expression);
% MATLAB side - receive and handle event
h.HTMLEventReceivedFcn = @handleEvent;
function handleEvent(src, event)
eventName = event.HTMLEventName;
eventData = event.HTMLEventData;
% Process event...
end
Use Case: Sending computed results or status updates to JavaScript
% MATLAB side - send custom event to JavaScript
sendEventToHTMLSource(h, "ResultChanged", result);
// JavaScript side - listen for custom event
htmlComponent.addEventListener("ResultChanged", function(event) {
document.getElementById("display").textContent = event.Data;
});
Use Case: Passing structured data between MATLAB and JavaScript
% MATLAB side - struct data gets JSON encoded automatically
itemData = struct("ItemName","Apple","Price",2,"Quantity",10);
h.Data = itemData;
// JavaScript side - access as object properties
htmlComponent.Data.ItemName // "Apple"
htmlComponent.Data.Price // 2
htmlComponent.Data.Quantity // 10
Important: decoding is automatic in both directions.
sendEventToMATLAB arrives on event.HTMLEventData already converted to a MATLAB struct. Do not call jsondecode; it will fail on a struct.sendEventToHTMLSource arrives on event.Data already as a JavaScript object. Do not call JSON.parse; it will fail on an object.{x0: 1} becomes a MATLAB struct('x0', 1), not struct('x_0', ...) or similar.double. Wrap field reads with double(data.x0) if you want to be defensive about types.ALWAYS set HTMLSource = 'trusted' when using local HTML files:
h.HTMLSource = fullfile(pwd, 'myapp.html');
% This is treated as trusted automatically for local files
MUST validate all input from JavaScript before processing in MATLAB
NEVER use eval() on user input without strict sanitization
ALWAYS restrict allowed characters in user input for expressions
ALWAYS wrap MATLAB event handlers in try-catch blocks:
function handleEvent(src, event)
eventName = event.HTMLEventName;
eventData = event.HTMLEventData;
try
% Process the event
result = processData(eventData);
% Send result back to JavaScript
sendEventToHTMLSource(src, 'ResultEvent', result);
catch ME
% Handle errors gracefully
fprintf('Error: %s\n', ME.message);
sendEventToHTMLSource(src, 'ErrorEvent', ME.message);
end
end
ALWAYS validate user input before processing:
function result = validateExpression(expression)
allowedChars = '0123456789+-*/.() ';
if ~all(ismember(expression, allowedChars))
error('Invalid characters in expression');
end
% Additional validation...
result = true;
end
Follow this directory structure:
project/
├── app.m # Main MATLAB function
├── app.html # HTML interface
├── README.md # Usage instructions
└── examples/ # Additional examples (optional)
MATLAB Side (calculator.m):
function calculator()
% Create main figure
fig = uifigure('Name', 'Calculator', 'Position', [100 100 400 500]);
% Create HTML component
h = uihtml(fig, 'Position', [25 25 350 450]);
h.HTMLSource = fullfile(pwd, 'calculator.html');
h.HTMLEventReceivedFcn = @(src, event) handleEvent(src, event);
end
function handleEvent(src, event)
eventName = event.HTMLEventName;
eventData = event.HTMLEventData;
try
switch eventName
case 'Calculate'
% Validate input
expression = char(eventData);
allowedChars = '0123456789+-*/.() ';
if ~all(ismember(expression, allowedChars))
error('Invalid characters in expression');
end
% Evaluate safely
result = eval(expression);
% Send result back
sendEventToHTMLSource(src, 'Result', num2str(result));
case 'Clear'
sendEventToHTMLSource(src, 'Result', '0');
end
catch ME
fprintf('Error: %s\n', ME.message);
sendEventToHTMLSource(src, 'Error', 'Invalid expression');
end
end
HTML Side (calculator.html):
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
margin: 0;
padding: 20px;
}
.calculator {
background: white;
border-radius: 10px;
padding: 20px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
.display {
width: 100%;
height: 60px;
font-size: 24px;
text-align: right;
padding: 10px;
border: 2px solid #ccc;
border-radius: 5px;
margin-bottom: 10px;
background: #f9f9f9;
}
.buttons {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
}
button {
padding: 20px;
font-size: 18px;
border: none;
border-radius: 5px;
cursor: pointer;
background: #667eea;
color: white;
transition: background 0.3s;
}
button:hover {
background: #764ba2;
}
.operator {
background: #ff6b6b;
}
.operator:hover {
background: #ee5a52;
}
</style>
<script type="text/javascript">
let currentExpression = '';
function setup(htmlComponent) {
window.htmlComponent = htmlComponent;
// Listen for results from MATLAB
htmlComponent.addEventListener("Result", function(event) {
document.getElementById("display").value = event.Data;
currentExpression = event.Data;
});
htmlComponent.addEventListener("Error", function(event) {
document.getElementById("display").value = "Error";
currentExpression = '';
});
}
function appendToDisplay(value) {
currentExpression += value;
document.getElementById("display").value = currentExpression;
}
function clearDisplay() {
currentExpression = '';
document.getElementById("display").value = '0';
window.htmlComponent.sendEventToMATLAB("Clear", "");
}
function calculate() {
if (currentExpression) {
window.htmlComponent.sendEventToMATLAB("Calculate", currentExpression);
}
}
</script>
</head>
<body>
<div class="calculator">
<input type="text" id="display" class="display" value="0" readonly>
<div class="buttons">
<button onclick="appendToDisplay('7')">7</button>
<button onclick="appendToDisplay('8')">8</button>
<button onclick="appendToDisplay('9')">9</button>
<button class="operator" onclick="appendToDisplay('/')">/</button>
<button onclick="appendToDisplay('4')">4</button>
<button onclick="appendToDisplay('5')">5</button>
<button onclick="appendToDisplay('6')">6</button>
<button class="operator" onclick="appendToDisplay('*')">*</button>
<button onclick="appendToDisplay('1')">1</button>
<button onclick="appendToDisplay('2')">2</button>
<button onclick="appendToDisplay('3')">3</button>
<button class="operator" onclick="appendToDisplay('-')">-</button>
<button onclick="appendToDisplay('0')">0</button>
<button onclick="appendToDisplay('.')">.</button>
<button onclick="calculate()">=</button>
<button class="operator" onclick="appendToDisplay('+')">+</button>
<button style="grid-column: span 4; background: #ff6b6b;" onclick="clearDisplay()">Clear</button>
</div>
</div>
</body>
</html>
MATLAB Side (visualizer.m):
function visualizer()
fig = uifigure('Name', 'Data Visualizer', 'Position', [100 100 800 600]);
% Create HTML component for controls
h = uihtml(fig, 'Position', [25 400 750 175]);
h.HTMLSource = fullfile(pwd, 'controls.html');
h.HTMLEventReceivedFcn = @(src, event) handleEvent(src, event, fig);
% Create axes for plotting
ax = uiaxes(fig, 'Position', [25 25 750 350]);
xlabel(ax, 'X');
ylabel(ax, 'Y');
title(ax, 'Interactive Plot');
end
function handleEvent(src, event, fig)
eventName = event.HTMLEventName;
eventData = event.HTMLEventData;
try
switch eventName
case 'UpdatePlot'
% Parse parameters from JavaScript
params = eventData;
frequency = params.frequency;
amplitude = params.amplitude;
plotType = params.plotType;
% Generate data
x = linspace(0, 4*pi, 200);
switch plotType
case 'sine'
y = amplitude * sin(frequency * x);
case 'cosine'
y = amplitude * cos(frequency * x);
case 'both'
y = amplitude * sin(frequency * x);
y2 = amplitude * cos(frequency * x);
end
% Find axes and plot
ax = findobj(fig, 'Type', 'axes');
cla(ax);
if strcmp(plotType, 'both')
plot(ax, x, y, 'LineWidth', 2);
hold(ax, 'on');
plot(ax, x, y2, 'LineWidth', 2);
hold(ax, 'off');
legend(ax, 'Sine', 'Cosine');
else
plot(ax, x, y, 'LineWidth', 2);
end
grid(ax, 'on');
% Send confirmation
sendEventToHTMLSource(src, 'PlotUpdated', 'Success');
end
catch ME
fprintf('Error: %s\n', ME.message);
sendEventToHTMLSource(src, 'Error', ME.message);
end
end
HTML Side (controls.html):
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
color: white;
margin: 0;
padding: 20px;
}
.controls {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 20px;
}
.control-group {
background: rgba(255,255,255,0.1);
padding: 15px;
border-radius: 8px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input[type="range"] {
width: 100%;
}
select, button {
width: 100%;
padding: 8px;
border-radius: 5px;
border: none;
font-size: 14px;
}
button {
background: #3498db;
color: white;
cursor: pointer;
margin-top: 10px;
transition: background 0.3s;
}
button:hover {
background: #2980b9;
}
</style>
<script type="text/javascript">
function setup(htmlComponent) {
window.htmlComponent = htmlComponent;
htmlComponent.addEventListener("PlotUpdated", function(event) {
console.log("Plot updated successfully");
});
}
function updatePlot() {
const frequency = parseFloat(document.getElementById("frequency").value);
const amplitude = parseFloat(document.getElementById("amplitude").value);
const plotType = document.getElementById("plotType").value;
const params = {
frequency: frequency,
amplitude: amplitude,
plotType: plotType
};
window.htmlComponent.sendEventToMATLAB("UpdatePlot", params);
}
function updateFreqLabel(value) {
document.getElementById("freqValue").textContent = value;
}
function updateAmpLabel(value) {
document.getElementById("ampValue").textContent = value;
}
</script>
</head>
<body>
<div class="controls">
<div class="control-group">
<label>Frequency: <span id="freqValue">1</span></label>
<input type="range" id="frequency" min="0.1" max="5" step="0.1" value="1"
oninput="updateFreqLabel(this.value)">
</div>
<div class="control-group">
<label>Amplitude: <span id="ampValue">1</span></label>
<input type="range" id="amplitude" min="0.1" max="5" step="0.1" value="1"
oninput="updateAmpLabel(this.value)">
</div>
<div class="control-group">
<label>Plot Type:</label>
<select id="plotType">
<option value="sine">Sine</option>
<option value="cosine">Cosine</option>
<option value="both">Both</option>
</select>
</div>
</div>
<button onclick="updatePlot()">Update Plot</button>
</body>
</html>
MATLAB Side (formProcessor.m):
function formProcessor()
fig = uifigure('Name', 'Form Processor', 'Position', [100 100 600 400]);
h = uihtml(fig, 'Position', [25 25 550 350]);
h.HTMLSource = fullfile(pwd, 'form.html');
h.HTMLEventReceivedFcn = @(src, event) handleEvent(src, event);
end
function handleEvent(src, event)
eventName = event.HTMLEventName;
eventData = event.HTMLEventData;
try
switch eventName
case 'SubmitForm'
% Extract form data
name = eventData.name;
email = eventData.email;
age = eventData.age;
% Validate data
if isempty(name) || isempty(email)
error('Name and email are required');
end
if ~contains(email, '@')
error('Invalid email address');
end
if age < 0 || age > 120
error('Invalid age');
end
% Process data (example: save to file or database)
fprintf('Processing form:\n');
fprintf(' Name: %s\n', name);
fprintf(' Email: %s\n', email);
fprintf(' Age: %d\n', age);
% Send success message
result = struct('status', 'success', ...
'message', 'Form submitted');
sendEventToHTMLSource(src, 'FormResult', result);
case 'ClearForm'
sendEventToHTMLSource(src, 'FormCleared', '');
end
catch ME
fprintf('Error: %s\n', ME.message);
result = struct('status', 'error', 'message', ME.message);
sendEventToHTMLSource(src, 'FormResult', result);
end
end
The HTML in the examples below is deliberately minimal to keep the focus on the MATLAB↔JS wiring. The inline styling (Arial,
#667eea → #764ba2purple-blue gradients, generic cards) is not a design reference. Those are exactly the "AI slop" tells to avoid. For any panel a user will actually see, take the visual system frommatlab-uihtml-designand follow its Design Guardrails.
flex-wrap is simpler:focus-visible state for keyboard usersmatlab-uihtml-design for ready-made styles). Avoid the default reflexes: Inter/Roboto/Arial fonts, purple-to-blue gradients, side-stripe border-left accents, gradient text, and over-rounded (≥32px) cardsfont-variant-numeric: tabular-nums so values don't jitter width@media (prefers-reduced-motion: reduce) with a crossfade/instant fallbackJavaScript Side:
htmlComponent.addEventListener("Error", function(event) {
// Display user-friendly error messages
alert("Error: " + event.Data);
});
MATLAB Side:
try
result = processInput(input);
sendEventToHTMLSource(src, 'Success', result);
catch ME
fprintf('Error: %s\n', ME.message);
sendEventToHTMLSource(src, 'Error', 'Processing failed');
end
Unit Testing - Test MATLAB functions independently
% Test individual processing functions
assert(validateExpression('2+2'), 'Validation should pass');
Integration Testing - Test HTML-MATLAB communication
% Test event handling with sample data
testEvent = struct('HTMLEventName', 'Calculate', 'HTMLEventData', '2+2');
handleEvent(h, testEvent);
User Testing - Test complete user workflows
Error Testing - Test error conditions
MATLAB Side: Use fprintf() to log events and data
fprintf('Received event: %s with data: %s\n', eventName, eventData);
JavaScript Side: Use browser developer tools (F12) to debug
console.log("Sending to MATLAB:", data);
Test each communication direction separately
Verify data types and formats
fprintf('Data type: %s\n', class(eventData));
fprintf('Data value: %s\n', string(eventData));
sendEventToMATLABsendEventToHTMLSourcesendEventToHTMLSource at intervalsWhen MATLAB drives an operation that takes more than a fraction of a second (an animation, an integration sweep, a streaming simulation), the patterns above need a few additions: a timer, resource cleanup, a way to cancel, and a way to tune the operation while it runs.
Use a MATLAB timer to drive the operation. Store the handle on fig.UserData so other event handlers (Reset, Stop, figure-close) can find and stop it.
function startAnimation(fig, ax, data)
stopAnimTimer(fig); % kill any prior run
state.data = data;
state.idx = 1;
state.line = animatedline(ax, 'Color', [0.65 0.55 0.94], 'LineWidth', 1.4);
state.step = 5; % points per tick (mutable from outside; see below)
tmr = timer( ...
'ExecutionMode', 'fixedSpacing', ...
'Period', 0.03, ...
'BusyMode', 'drop', ... % skip ticks if callback overruns
'TimerFcn', @(s, ~) animStep(s, fig));
tmr.UserData = state;
fig.UserData.AnimTimer = tmr;
start(tmr);
end
funct
<!-- Content truncated for initial SEO render. Open the source file tab for the full file. -->
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer