or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

authentication-system.mdchat-interface.mdcore-functions.mdcustom-components.mdindex.mdlayout-components.mdlinks-system.mdpane-system.mdparameter-integration.mdpipeline-system.mdtemplate-system.mdwidget-system.md

custom-components.mddocs/

0

# Custom Components

1

2

Classes for extending Panel with custom functionality using Python, JavaScript, React, or ESM modules. Custom components enable developers to create specialized widgets and visualizations that integrate seamlessly with Panel's component system.

3

4

## Capabilities

5

6

### Python-Based Components

7

8

Create custom components using pure Python with reactive parameters and custom rendering logic.

9

10

```python { .api }

11

class PyComponent:

12

"""

13

Python-based custom component for creating widgets with pure Python.

14

15

Parameters:

16

- _template: HTML template string for component rendering

17

- _stylesheets: List of CSS stylesheets to include

18

- _extension_name: Name for the component extension

19

- **params: Component parameters that become reactive

20

"""

21

```

22

23

### JavaScript Components

24

25

Create custom components using JavaScript for client-side interactivity and custom behavior.

26

27

```python { .api }

28

class JSComponent:

29

"""

30

JavaScript-based component for client-side custom widgets.

31

32

Parameters:

33

- _esm: ECMAScript module code or file path

34

- _template: HTML template for the component

35

- _stylesheets: CSS stylesheets to include

36

- _importmap: Import map for module dependencies

37

- **params: Component parameters

38

"""

39

```

40

41

### React Components

42

43

Create custom components using React for building complex interactive interfaces.

44

45

```python { .api }

46

class ReactComponent:

47

"""

48

React-based component for building React-powered widgets.

49

50

Parameters:

51

- _template: React JSX template

52

- _stylesheets: CSS stylesheets for styling

53

- _importmap: Import map for React dependencies

54

- **params: Component parameters passed as React props

55

"""

56

```

57

58

### ESM Module Components

59

60

Create components from ECMAScript modules for modern JavaScript functionality.

61

62

```python { .api }

63

class ReactiveESM:

64

"""

65

ESM module-based component with reactive parameter binding.

66

67

Parameters:

68

- _esm: ECMAScript module source code or file path

69

- _template: HTML template for component

70

- _stylesheets: CSS stylesheets

71

- _importmap: Module import map

72

- **params: Reactive parameters

73

"""

74

```

75

76

### AnyWidget Integration

77

78

Create components compatible with the AnyWidget specification for cross-framework compatibility.

79

80

```python { .api }

81

class AnyWidgetComponent:

82

"""

83

AnyWidget-compatible component for cross-framework integration.

84

85

Parameters:

86

- _esm: JavaScript module implementing AnyWidget interface

87

- _css: CSS styles for the component

88

- **params: Widget parameters

89

"""

90

```

91

92

## Usage Examples

93

94

### Basic Python Component

95

96

```python

97

import panel as pn

98

import param

99

100

class CounterComponent(pn.custom.PyComponent):

101

102

value = param.Integer(default=0)

103

step = param.Integer(default=1)

104

105

_template = """

106

<div class="counter">

107

<button id="decrement" onclick="${script('decrement')}">-</button>

108

<span class="value">${value}</span>

109

<button id="increment" onclick="${script('increment')}">+</button>

110

</div>

111

"""

112

113

_stylesheets = ["""

114

.counter {

115

display: flex;

116

align-items: center;

117

gap: 10px;

118

font-family: sans-serif;

119

}

120

.counter button {

121

padding: 5px 10px;

122

font-size: 16px;

123

border: 1px solid #ccc;

124

background: #f8f9fa;

125

cursor: pointer;

126

}

127

.counter .value {

128

font-size: 18px;

129

font-weight: bold;

130

min-width: 40px;

131

text-align: center;

132

}

133

"""]

134

135

def _handle_increment(self, event):

136

self.value += self.step

137

138

def _handle_decrement(self, event):

139

self.value -= self.step

140

141

# Use the custom component

142

counter = CounterComponent(value=10, step=2)

143

```

144

145

### JavaScript Component

146

147

```python

148

class ChartComponent(pn.custom.JSComponent):

149

150

data = param.List(default=[])

151

chart_type = param.Selector(default='line', objects=['line', 'bar', 'pie'])

152

153

_esm = """

154

export function render({ model, el }) {

155

// Create chart using Chart.js or D3

156

const canvas = document.createElement('canvas');

157

el.appendChild(canvas);

158

159

const ctx = canvas.getContext('2d');

160

const chart = new Chart(ctx, {

161

type: model.chart_type,

162

data: {

163

datasets: [{

164

data: model.data,

165

backgroundColor: 'rgba(75, 192, 192, 0.2)',

166

borderColor: 'rgba(75, 192, 192, 1)',

167

}]

168

},

169

options: {

170

responsive: true,

171

plugins: {

172

legend: { display: false }

173

}

174

}

175

});

176

177

// Update chart when parameters change

178

model.on('change:data', () => {

179

chart.data.datasets[0].data = model.data;

180

chart.update();

181

});

182

183

model.on('change:chart_type', () => {

184

chart.config.type = model.chart_type;

185

chart.update();

186

});

187

}

188

"""

189

190

_importmap = {

191

"imports": {

192

"chart.js": "https://cdn.jsdelivr.net/npm/chart.js@4.4.0/+esm"

193

}

194

}

195

196

# Use the chart component

197

chart = ChartComponent(

198

data=[10, 20, 30, 25, 15],

199

chart_type='bar'

200

)

201

```

202

203

### React Component

204

205

```python

206

class ReactButton(pn.custom.ReactComponent):

207

208

clicks = param.Integer(default=0)

209

label = param.String(default="Click me")

210

variant = param.Selector(default='primary', objects=['primary', 'secondary', 'success', 'danger'])

211

212

_template = """

213

export default function ReactButton({ model }) {

214

const [clicks, setClicks] = React.useState(model.clicks);

215

216

const handleClick = () => {

217

const newClicks = clicks + 1;

218

setClicks(newClicks);

219

model.clicks = newClicks;

220

};

221

222

return (

223

<button

224

className={`btn btn-${model.variant}`}

225

onClick={handleClick}

226

style={{

227

padding: '8px 16px',

228

fontSize: '16px',

229

border: 'none',

230

borderRadius: '4px',

231

cursor: 'pointer',

232

backgroundColor: getVariantColor(model.variant)

233

}}

234

>

235

{model.label} ({clicks})

236

</button>

237

);

238

}

239

240

function getVariantColor(variant) {

241

const colors = {

242

primary: '#007bff',

243

secondary: '#6c757d',

244

success: '#28a745',

245

danger: '#dc3545'

246

};

247

return colors[variant] || colors.primary;

248

}

249

"""

250

251

_importmap = {

252

"imports": {

253

"react": "https://esm.sh/react@18",

254

"react-dom": "https://esm.sh/react-dom@18"

255

}

256

}

257

258

# Use the React component

259

react_btn = ReactButton(label="React Button", variant='success')

260

```

261

262

### Complex Interactive Component

263

264

```python

265

import param

266

import pandas as pd

267

268

class DataExplorer(pn.custom.PyComponent):

269

270

data = param.DataFrame()

271

selected_columns = param.List(default=[])

272

chart_type = param.Selector(default='scatter', objects=['scatter', 'line', 'bar', 'histogram'])

273

274

_template = """

275

<div class="data-explorer">

276

<div class="controls">

277

<h3>Data Explorer</h3>

278

<div class="column-selector">

279

<label>Select Columns:</label>

280

<select multiple size="5" onchange="${script('update_columns')}">

281

{% for col in data.columns %}

282

<option value="{{ col }}"

283

{% if col in selected_columns %}selected{% endif %}>

284

{{ col }}

285

</option>

286

{% endfor %}

287

</select>

288

</div>

289

<div class="chart-type">

290

<label>Chart Type:</label>

291

<select onchange="${script('update_chart_type')}">

292

<option value="scatter" {% if chart_type == 'scatter' %}selected{% endif %}>Scatter</option>

293

<option value="line" {% if chart_type == 'line' %}selected{% endif %}>Line</option>

294

<option value="bar" {% if chart_type == 'bar' %}selected{% endif %}>Bar</option>

295

<option value="histogram" {% if chart_type == 'histogram' %}selected{% endif %}>Histogram</option>

296

</select>

297

</div>

298

</div>

299

<div class="preview">

300

{% if selected_columns %}

301

<h4>Selected Data Preview:</h4>

302

<div class="data-preview">

303

{{ data[selected_columns].head().to_html() }}

304

</div>

305

{% endif %}

306

</div>

307

</div>

308

"""

309

310

_stylesheets = ["""

311

.data-explorer {

312

display: flex;

313

flex-direction: column;

314

gap: 20px;

315

padding: 20px;

316

border: 1px solid #ddd;

317

border-radius: 8px;

318

background: #f9f9f9;

319

}

320

.controls {

321

display: flex;

322

gap: 20px;

323

align-items: flex-start;

324

}

325

.column-selector select, .chart-type select {

326

padding: 5px;

327

min-width: 150px;

328

}

329

.data-preview {

330

max-height: 300px;

331

overflow: auto;

332

background: white;

333

padding: 10px;

334

border-radius: 4px;

335

}

336

"""]

337

338

def _handle_update_columns(self, event):

339

# Extract selected columns from event

340

selected = event.target.selectedValues

341

self.selected_columns = list(selected)

342

343

def _handle_update_chart_type(self, event):

344

self.chart_type = event.target.value

345

346

@param.depends('selected_columns', 'chart_type', watch=True)

347

def _update_visualization(self):

348

# Trigger re-render when parameters change

349

self._render()

350

351

# Use the data explorer

352

df = pd.DataFrame({

353

'x': range(100),

354

'y': [i**2 for i in range(100)],

355

'category': ['A', 'B'] * 50

356

})

357

358

explorer = DataExplorer(data=df)

359

```

360

361

### AnyWidget Component

362

363

```python

364

class AnyWidgetSlider(pn.custom.AnyWidgetComponent):

365

366

value = param.Number(default=50)

367

min = param.Number(default=0)

368

max = param.Number(default=100)

369

step = param.Number(default=1)

370

371

_esm = """

372

function render({ model, el }) {

373

const slider = document.createElement('input');

374

slider.type = 'range';

375

slider.min = model.get('min');

376

slider.max = model.get('max');

377

slider.step = model.get('step');

378

slider.value = model.get('value');

379

380

slider.addEventListener('input', () => {

381

model.set('value', parseFloat(slider.value));

382

model.save_changes();

383

});

384

385

model.on('change:value', () => {

386

slider.value = model.get('value');

387

});

388

389

el.appendChild(slider);

390

}

391

392

export default { render };

393

"""

394

395

_css = """

396

input[type="range"] {

397

width: 100%;

398

height: 20px;

399

background: #ddd;

400

outline: none;

401

border-radius: 10px;

402

}

403

"""

404

405

# Use the AnyWidget component

406

slider = AnyWidgetSlider(value=75, min=0, max=200, step=5)

407

```

408

409

## Integration Patterns

410

411

### Custom Component with Panel Widgets

412

413

```python

414

class ComponentWithControls(pn.custom.PyComponent):

415

416

def __init__(self, **params):

417

super().__init__(**params)

418

419

# Create control widgets

420

self.controls = pn.Column(

421

pn.widgets.ColorPicker(name="Color", value="#ff0000"),

422

pn.widgets.IntSlider(name="Size", start=10, end=100, value=50),

423

pn.widgets.Select(name="Shape", options=['circle', 'square', 'triangle'])

424

)

425

426

# Bind control changes to component updates

427

for widget in self.controls:

428

widget.param.watch(self._update_from_controls, 'value')

429

430

def _update_from_controls(self, event):

431

# Update component based on control values

432

self._render()

433

434

def panel(self):

435

return pn.Row(self.controls, self)

436

437

component = ComponentWithControls()

438

```

439

440

### Custom Component Factory

441

442

```python

443

def create_custom_widget(widget_type, **config):

444

"""Factory function for creating custom widgets"""

445

446

if widget_type == 'counter':

447

return CounterComponent(**config)

448

elif widget_type == 'chart':

449

return ChartComponent(**config)

450

elif widget_type == 'data_explorer':

451

return DataExplorer(**config)

452

else:

453

raise ValueError(f"Unknown widget type: {widget_type}")

454

455

# Use the factory

456

custom_widgets = [

457

create_custom_widget('counter', value=0, step=1),

458

create_custom_widget('chart', data=[1,2,3,4,5], chart_type='line'),

459

create_custom_widget('data_explorer', data=df)

460

]

461

```