A zero-dependency click popover component with multi-page carousel support, paired with a PHP class that renders directly from database data.
bp-popnote is a lightweight, self-contained popover component designed for desktop web applications. It works with any PHP page by including a single JS file — no Bootstrap, no jQuery, no build step required.
The companion BpPopnote.php class lets you render popover triggers
and their content directly from PHP variables or database query results,
handling ID generation, HTML escaping, and newline conversion automatically.
That's it. No npm install, no composer, no bundler.
<!-- 1. Add data attributes to any element --> <span data-popover-title="Aspirin" data-popover-content="Analgesic, antipyretic, anti-inflammatory." data-popover-theme="warning" >Aspirin</span> <!-- 2. Include the script before </body> --> <script src="bp-popnote.js"></script>
<?php require_once 'BpPopnote.php'; $drug = $pdo->query("SELECT * FROM drugs WHERE id = 1")->fetch(); ?> <!-- Inline short text --> <?= BpPopnote::inline($drug['name'], $drug['indication'], ['theme' => 'warning']) ?> <!-- Multi-page carousel from DB row --> <?= BpPopnote::fromArray($drug['name'], 'Drug Info', [ 'Dosage' => $drug['dosage'], 'Contraindication' => $drug['contraindication'], 'Interactions' => $drug['interactions'], ], ['theme' => 'warning']) ?> <!-- Script tag (call once, renders only once even if called multiple times) --> <?= BpPopnote::script('/assets/bp-popnote.js') ?>
<span data-popover-title="Title" data-popover-content="Short text or simple HTML" data-popover-theme="info" >trigger text</span>
<span data-popover-title="Title" data-popover-target="#my-note">trigger</span> <template id="my-note"> <table>...</table> <p>Any HTML here.</p> </template>
Place two or more direct child <section> elements inside the
<template>. The carousel activates automatically — no extra
attribute needed.
<span data-popover-title="Step by Step" data-popover-target="#steps" data-popover-interval="4000" >View Steps</span> <template id="steps"> <section>Step 1 content</section> <section>Step 2 content</section> <section>Step 3 content</section> </template>
BpPopnote::inline()Short plain text or simple HTML. escape defaults to true.
BpPopnote::inline( 'trigger label', 'description text', ['title' => 'Tooltip Title', 'theme' => 'info'] )
BpPopnote::html()Large HTML block. You assemble the HTML; escape defaults to false.
$html = '<table>...</table>'; BpPopnote::html($row['name'], $html, [ 'title' => 'Detail', 'theme' => 'warning', ])
BpPopnote::carousel()Multi-page carousel. Each array key becomes a page heading; values are HTML strings.
BpPopnote::carousel($emp['name'], 'Employee Info', [ 'Profile' => '<p>...</p>', 'Health' => '<p>...</p>', 'Review' => '<p>...</p>', ], ['theme' => 'info', 'interval' => 4000])
BpPopnote::fromArray()
Pass a DB row directly. Every value is treated as plain text —
htmlspecialchars + nl2br applied automatically.
Each key becomes a carousel page heading.
BpPopnote::fromArray($product['name'], 'Product Detail', [ 'Category' => $product['category'], 'Description' => $product['description'], // \n auto → <br> 'Stock' => $product['stock'], ], ['theme' => 'highlight'])
BpPopnote::script()
Outputs the <script> tag and optional global config.
Safe to call multiple times — renders only once.
BpPopnote::script('/assets/bp-popnote.js', [ 'theme' => 'dark', 'maxWidth' => '520px', 'borderStyle' => 'solid', 'carousel' => ['interval' => 3000], ])
Set globally via PopoverConfig.set({ theme: 'info' }) or per-element via data-popover-theme / PHP 'theme' option.
PopoverConfig.addTheme('brand', { bg: '#0a0f1e', titleColor: '#f0a500', borderColor: '#f0a500', });
| Attribute | Values | Default | Description |
|---|---|---|---|
data-popover-title | string | — | Popover header text |
data-popover-content | string | — | Inline content (short text / HTML) |
data-popover-target | #id | — | Points to a <template> element |
data-popover-theme | theme name | dark | Visual theme |
data-popover-placement | top / bottom / left / right | top | Preferred direction (auto-flips) |
data-popover-border | solid / dashed / dotted | solid | Border style |
data-popover-fontsize | CSS value | 1rem | Font size inside popover |
data-popover-maxwidth | CSS value | 560px | Max width |
data-popover-arrow | true / false | true | Show directional arrow |
data-popover-hint | true / false | true | Dashed underline on trigger |
data-popover-interval | ms / 0 | 3000 | Carousel auto-play interval; 0 = off |
data-popover-carousel-anim | slide / crossfade | slide | Carousel transition style |
$options array keys| Key | Type | Default | Description |
|---|---|---|---|
title | string | same as $label | Popover header text |
theme | string | dark | Theme name |
placement | string | top | Preferred direction |
border | string | solid | Border style |
fontsize | string | 1rem | Font size |
maxwidth | string | — | Max width override |
arrow | bool | true | Show arrow |
hint | bool | true | Show dashed underline |
interval | int | 3000 | Carousel interval ms |
carousel_anim | string | slide | Carousel animation |
escape | bool | method-dependent | Auto htmlspecialchars + nl2br |
tag | string | span | Trigger element tag name |
class | string | — | Extra CSS class on trigger |
PopoverConfig.set({ theme: 'dark', // default theme maxWidth: '560px', offset: 8, // px gap between trigger and popover fontSize: '1rem', borderStyle: 'solid', // solid | dashed | dotted placement: 'top', carousel: { animation: 'slide', // slide | crossfade interval: 3000, // ms; 0 = no auto-play }, });
| Method | escape default | \n → <br> | Use when |
|---|---|---|---|
inline() | true | ✅ auto | Short plain-text DB fields |
html() | false | manual | You assemble the HTML yourself |
carousel() | false | manual | Each page is an HTML string you build |
fromArray() | true (forced) | ✅ auto | Dump a DB row directly, no HTML needed |
html() or carousel(),
always call htmlspecialchars() on untrusted field values before
embedding them. For fields containing newlines, chain
nl2br(htmlspecialchars($value, ENT_QUOTES, 'UTF-8')).
MIT License — free to use, modify, and distribute in personal and commercial projects.