{"id":16894300,"url":"https://github.com/jsor/ctrly","last_synced_at":"2025-07-31T23:03:51.219Z","repository":{"id":66311863,"uuid":"145554099","full_name":"jsor/ctrly","owner":"jsor","description":"Lightweight and dependency-free content toggling with a focus on accessibility.","archived":false,"fork":false,"pushed_at":"2021-07-19T12:09:34.000Z","size":1055,"stargazers_count":8,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-11T13:53:16.244Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jsor.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2018-08-21T11:24:34.000Z","updated_at":"2021-07-19T12:09:37.000Z","dependencies_parsed_at":null,"dependency_job_id":"7c794e72-cbde-4566-b2e0-9cd761f19d08","html_url":"https://github.com/jsor/ctrly","commit_stats":{"total_commits":92,"total_committers":2,"mean_commits":46.0,"dds":0.03260869565217395,"last_synced_commit":"663e7bc8cd619c6c44f6230f86e638ecb68fbbcb"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/jsor/ctrly","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsor%2Fctrly","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsor%2Fctrly/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsor%2Fctrly/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsor%2Fctrly/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jsor","download_url":"https://codeload.github.com/jsor/ctrly/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsor%2Fctrly/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268133609,"owners_count":24201374,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-31T02:00:08.723Z","response_time":66,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-13T17:18:17.152Z","updated_at":"2025-07-31T23:03:51.180Z","avatar_url":"https://github.com/jsor.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"ctrly\n=====\n\nLightweight and dependency-free content toggling with a focus on accessibility.\n\n[![Build Status](https://travis-ci.com/jsor/ctrly.svg?branch=main)](https://travis-ci.com/jsor/ctrly)\n[![BrowserStack Status](https://automate.browserstack.com/badge.svg?badge_key=ODRaY1N5SUhNOXNmZXVoVUJDTytSVE1tdHo1aWxzaENwOFdjVk93VDNPVT0tLXVMNXN0SFBQOFNjUk5kTHl2dmo1NGc9PQ==--3a2198d90bb0b3c048e5beec9217e50a40da2095)](https://automate.browserstack.com/public-build/ODRaY1N5SUhNOXNmZXVoVUJDTytSVE1tdHo1aWxzaENwOFdjVk93VDNPVT0tLXVMNXN0SFBQOFNjUk5kTHl2dmo1NGc9PQ==--3a2198d90bb0b3c048e5beec9217e50a40da2095)\n\n\nAbout\n-----\n\nctrly is a lightweight library which tries to solve 80% of the use-cases where\na **control** (usually a `\u003cbutton\u003e` element) toggles the visibility of a \n**target** element.\n\nIt can be used to implement dropdown or off-canvas menus, modals, accordions\nand similar UI elements. Example implementations can be found in the \n[`examples/` directory](examples/).\n\n**Demos**: [**Accordion**](https://sorgalla.com/ctrly/examples/accordion/) • [**Dropdown**](https://sorgalla.com/ctrly/examples/dropdown/) • [**Offcanvas**](https://sorgalla.com/ctrly/examples/offcanvas/)\n\nMinified and gzipped, the total footprint weights about 3kB.\n\nInstallation\n------------\n\nThe recommended way to install ctrly is via [npm](https://www.npmjs.com/package/ctrly).\n\n```bash\nnpm install ctrly\n```\n\nAfter installation, `ctrly` can be imported as a module.\n\n```js\nimport ctrly from 'ctrly';\n```\n\nThe latest version can also be downloaded or included from \n[unpkg](https://unpkg.com/ctrly) or\n[jsDelivr](https://cdn.jsdelivr.net/npm/ctrly).\n\n```html\n\u003cscript src=\"/path/to/ctrly.js\"\u003e\u003c/script\u003e\n```\n\nThe `ctrly()` function is then globally available.\n\nUsage\n-----\n\nA typical setup includes a **control** (usually a `\u003cbutton\u003e` element) which\ntoggles the visibility of a **target** element.\n\nThe control must have a `data-ctrly` attribute which must contain the ID of the\ntarget.\n\n```html\n\u003cbutton data-ctrly=\"my-target\"\u003eToggle\u003c/button\u003e\n\u003csection id=\"my-target\"\u003eYou clicked the toggle to make me visible\u003c/section\u003e\n```\n\nTo initialize all controls, the `ctrly()` function must be called once.\n\n```js\nctrly();\n```\n\nctrly then adds all required ARIA attributes:\n\n* The `aria-controls` and `aria-expanded` attributes to the control.\n* The `aria-hidden` and `aria-labelledby` to the target.\n\nIf the control does not have an `id` attribute, ctrly will add an auto-generated\nID.\n\nThe fully generated HTML looks like the following.\n\n```html\n\u003cbutton\n    data-ctrly=\"my-target\"\n    id=\"ctrly-control-1\"\n    aria-controls=\"my-target\"\n    aria-expanded=\"false\"\n\u003e\n    Toggle\n\u003c/button\u003e\n\u003csection\n    id=\"my-target\"\n    aria-hidden=\"false\"\n    aria-labelledby=\"ctrly-control-1\"\n\u003e\n    You clicked the toggle to make me visible\n\u003c/section\u003e\n```\n\n**Note:** ctrly does not ship with any default CSS which shows and hides the\ntarget element as it makes no assumptions on how the visibility is controlled.\n\nThis must be implemented depending on the `aria-hidden` attribute which is\ntoggled to either `false` or `true` by ctrly.\n\n```css\n/* Toggle via the display property */\n.target-selector[aria-hidden=\"true\"] {\n    display: none;\n}\n\n/* Toggle via the visibility property */\n.target-selector[aria-hidden=\"true\"] {\n    visibility: hidden;\n}\n```\n\nIt is also good practice to hide the controls if JavaScript is disabled.\n\nThis can be done depending on the presence of the `aria-controls` attribute\nadded by ctrly.\n\n```css\n.control-selector:not([aria-controls]) {\n    display: none;\n}\n```\n\nWhile it is highly recommended to use `\u003cbutton\u003e` elements as controls, ctrly\nalso supports other HTML elements.\n\n```html\n\u003cspan data-ctrly=\"my-target\"\u003eToggle\u003c/button\u003e\n```\n\nThe fully generated HTML looks like the following (note the additional \n`tabindex`, `role` and `aria-pressed` attributes).\n\n```html\n\u003cspan\n    data-ctrly=\"my-target\"\n    id=\"ctrly-control-1\"\n    aria-controls=\"my-target\"\n    aria-expanded=\"false\"\n    tabindex=\"0\"\n    role=\"button\"\n    aria-pressed=\"false\"\n\u003e\n    Toggle\n\u003c/span\u003e\n```\n\nAPI\n---\n\nThe return value of the `ctrly()` function is an object with the following \nfunctions.\n\n* [closeAll](#closeall)\n* [destroy](#destroy)\n* [init](#init)\n\n### closeAll\n\nThis function closes all open targets.\n\n#### Example\n\n```js\nconst { closeAll } = ctrly();\n\ncloseAll();\n\n```\n\n### destroy\n\nThis function reverts all elements to their initial state and unbinds all event\nlisteners.\n\n#### Example\n\n```js\nconst { destroy } = ctrly();\n\ndestroy();\n\n```\n\n### init\n\nThis function (re)initializes all controls. This can be useful after the DOM\nhas been updated, eg. controls have been added dynamically.\n\n#### Example\n\n```js\nconst { init } = ctrly();\n\ninit();\n\n```\n\nOptions\n-------\n\nctrly's behavior can be controlled by passing an options object as the first \nargument.\n\n```js\nctrly({\n    // Options...\n});\n```\n\nThe following options are available.\n\n* [selector](#selector)\n* [context](#context)\n* [focusTarget](#focustarget)\n* [closeOnBlur](#closeonblur)\n* [closeOnEsc](#closeonesc)\n* [closeOnOutsideClick](#closeonoutsideclick)\n* [closeOnScroll](#closeonscroll)\n* [trapFocus](#trapfocus)\n* [allowMultiple](#allowmultiple)\n* [on](#on)\n* [autoInit](#autoinit)\n\n### selector\n\n*Default:* `[data-ctrly]`\n\nA selector for the control elements.\n\n#### Example\n\n```html\n\u003cbutton class=\"my-control\" data-ctrly=\"my-target\"\u003eToggle\u003c/button\u003e\n```\n\n```js\nctrly({\n    selector: '.my-control'\n});\n```\n\n### context\n\n*Default:* `null`\n\nA selector to group targets together. Can be used in combination with the\n[allowMultiple](#allowmultiple) option to allow or disallow multiple open\ntargets inside a context.\n\nSee the [accordion example](examples/accordion/) for a use-case.\n\n#### Example\n\n```html\n\u003cdiv class=\"my-context\"\u003e\n    \u003cbutton data-ctrly=\"my-target\"\u003eToggle\u003c/button\u003e\n\u003c/div\u003e\n\u003cdiv class=\"my-context\"\u003e\n    \u003cbutton data-ctrly=\"my-target\"\u003eToggle\u003c/button\u003e\n\u003c/div\u003e\n```\n\n```js\nctrly({\n    context: '.my-context'\n});\n```\n\n### focusTarget\n\n*Default:* `true`\n\nBy default, once the target becomes visible, the focus is shifted to the first\nfocusable element inside the target. Passing `false` as an option disables this \nbehavior.\n\n#### Example\n\n```js\nctrly({\n    focusTarget: false\n});\n```\n\n### closeOnBlur\n\n*Default:* `true`\n\nBy default, targets are closed when the focus is shifted from an element inside\nthe target to an element outside the target. Passing `false` as an option\ndisables this behavior.\n\n\u003e This setting is always `false` if [`trapFocus`](#trapfocus) is set\n  to `true`.\n\n#### Example\n\n```js\nctrly({\n    closeOnBlur: false\n});\n```\n\n### closeOnEsc\n\n*Default:* `true`\n\nBy default, targets are closed when the \u003ckbd\u003eESC\u003c/kbd\u003e key is pressed. Passing\n`false` as an option disables this behavior.\n\n#### Example\n\n```js\nctrly({\n    closeOnEsc: false\n});\n```\n\n### closeOnOutsideClick\n\n*Default:* `true`\n\nBy default, targets are closed when there is a mouse click outside the target.\nPassing `false` as an option disables this behavior.\n\n#### Example\n\n```js\nctrly({\n    closeOnOutsideClick: false\n});\n```\n\n### closeOnScroll\n\n*Default:* `false`\n\nPassing `true` as an option closes a target when the window is scrolled and\nthe mouse is currently not inside the target element.\n\n#### Example\n\n```js\nctrly({\n    closeOnScroll: true\n});\n```\n\n### trapFocus\n\n*Default:* `false`\n\nPassing `true` as an option ensures that \u003ckbd\u003eTAB\u003c/kbd\u003e and\n\u003ckbd\u003eSHIFT\u003c/kbd\u003e+\u003ckbd\u003eTAB\u003c/kbd\u003e do not move focus outside the target.\n\n#### Example\n\n```js\nctrly({\n    trapFocus: true\n});\n```\n\n### allowMultiple\n\n*Default:* `false`\n\nBy default, if a target becomes visible, all other open targets are closed.\nPassing `true` as an option allows multiple targets to be opened at the same \ntime.\n\nThis can be combined with the [context](#context) option to only allow multiple\nopen targets inside a context element. \nSee the [accordion example](examples/accordion/) for a use-case.\n\n\u003e To allow multiple open targets, [`closeOnBlur`](#closeonblur) must be set to\n  `false`.\n\n#### Example\n\n```js\nctrly({\n    allowMultiple: true\n});\n```\n\n### on\n\n*Default:* `{}`\n\nAllows to define event callbacks as `{event: callback}` pairs.\n\n#### Example\n\n```js\nctrly({\n    on: {\n        open: target =\u003e {\n            // Called before a target is opened\n        },\n        opened: target =\u003e {\n            // Called after a target has been opened\n        },\n        close: target =\u003e {\n            // Called before a target is closed\n        },\n        closed: target =\u003e {\n            // Called after a target has been closed\n        }\n    }\n});\n```\n\nMore information about the event callbacks can be found in the\n[Events section](#events).\n\n### autoInit\n\n*Default:* `true`\n\nBy default, initialization is done when calling `ctrly()`. Passing `false` as\nan option disables this behavior and the [`init()`](#init) method must be called\nmanually.\n\n#### Example\n\n```js\nconst { init } = ctrly({\n    autoInit: false\n});\n\ninit();\n```\n\nEvents\n------\n\nctrly triggers several events when a target is opened or closed.\n\nThere are 2 ways to bind listeners to the events.\n\n1. Through the [`on` option](#on).\n2. Through DOM event listeners on the target element (the DOM event names are\n   prefixed with `ctrly:`, eg. `ctrly:open`).\n\nThe following events are available.\n\n* [open](#open)\n* [opened](#open)\n* [close](#close)\n* [closed](#closed)\n\n### open\n\nTriggered before the target element is opened.\n\n#### Example\n\n```js\nctrly({\n    on: {\n        open: target =\u003e {\n            target.classList.add('is-opening');\n        }\n    }\n});\n\n// or\n\ndocument.getElementById('my-target').addEventListener('ctrly:open', e =\u003e {\n    const target = e.target;\n\n    target.classList.add('is-opening');\n});\n```\n\n### opened\n\nTriggered after the target element has been opened.\n\n#### Example\n\n```js\nctrly({\n    on: {\n        opened: target =\u003e {\n            target.classList.remove('is-opening');\n        }\n    }\n});\n\n// or\n\ndocument.getElementById('my-target').addEventListener('ctrly:opened', e =\u003e {\n    const target = e.target;\n\n    target.classList.remove('is-opening');\n});\n```\n\n### close\n\nTriggered before the target element is closed.\n\n#### Example\n\n```js\nctrly({\n    on: {\n        close: target =\u003e {\n            target.classList.add('is-closing');\n        }\n    }\n});\n\n// or\n\ndocument.getElementById('my-target').addEventListener('ctrly:close', e =\u003e {\n    const target = e.target;\n\n    target.classList.add('is-closing');\n});\n```\n\n### closed\n\nTriggered after the target element has been opened.\n\n#### Example\n\n```js\nctrly({\n    on: {\n        closed: target =\u003e {\n            target.classList.remove('is-closing');\n        }\n    }\n});\n\n// or\n\ndocument.getElementById('my-target').addEventListener('ctrly:closed', e =\u003e {\n    const target = e.target;\n\n    target.classList.remove('is-closing');\n});\n```\n\nThank You\n---------\n\n* [BrowserStack](https://www.browserstack.com/) for providing free VMs for automated testing.\n* [GitHub](https://github.com/) for providing free Git repository hosting.\n* [npm](https://www.npmjs.com/) for providing the package manager for JavaScript.\n* [TravisCI](https://travis-ci.org/) for providing a free build server.\n\nLicense\n-------\n\nCopyright (c) 2018-2021 Jan Sorgalla.\nReleased under the [MIT](LICENSE) license.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjsor%2Fctrly","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjsor%2Fctrly","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjsor%2Fctrly/lists"}