- HTML
HTML dialog要素でモーダル実装|コピペ完成版とa11y対応
HTML の dialog 要素は、showModal() を1回呼ぶだけで「最上位レイヤーへの表示・背面の操作無効化(inert)・Esc で閉じる・フォーカスの移動と復帰」まで備えたモーダルを作れる標準要素です。 以前はライブラリに頼っていたフォーカストラップやスクロール固定の多くが、ブラウザ側の仕事になりました。
この記事では、まず メソッド・属性・疑似要素の早見表 で全体像をつかみ、次に HTML + CSS + JS を1ブロックにまとめたコピペ完成版(開閉、Esc、外側クリック、スクロール固定、@starting-style アニメーション、closedby、autofocus、aria-labelledby)を示します。そのうえで Playwright(Chromium 141)で「開いたときフォーカスはどこか」「背面は本当に操作できないか」「returnValue に何が入るか」「show() との違い」を実際に動かした結果 を表にし、popover との使い分け、React / Vue の最小例、実際のエラー文つきのつまずきポイントまでまとめました。
dialog 要素の早見表
| 書き方 | 役割 | ポイント |
|---|---|---|
dialog.show() |
非モーダルで開く | 背面はそのまま操作できる。Esc で閉じない。::backdrop は描画されない |
dialog.showModal() |
モーダルで開く | 最上位レイヤーに表示。背面は inert(操作不可)。Esc で閉じる。フォーカスが中へ移る |
dialog.close(value) |
閉じる | 引数を渡すと returnValue に入る。閉じている dialog に呼んでも何も起きない |
dialog.requestClose(value) |
「閉じる要求」 | 先に cancel イベントを発火し、preventDefault() で閉じるのを止められる |
dialog.returnValue |
閉じた理由 | <form method="dialog"> のボタンの value が自動で入る文字列 |
open 属性 |
開いている状態 | HTML に直接書くと非モーダルで開く。JS では dialog.open を読み取り専用で使う |
closedby="any" |
閉じ方の指定 | any=外側クリックでも閉じる、closerequest=Esc と JS のみ(モーダルの既定)、none=JS のみ |
<form method="dialog"> |
送信で閉じる | ページ遷移せずに閉じ、押したボタンの value が returnValue になる |
::backdrop |
背面を覆う層 | showModal() のときだけ描画。既定は rgba(0, 0, 0, 0.1) |
:modal / :open |
状態セレクタ | :modal はモーダルで開いているとき、:open は開いているとき([open] でも代用可) |
cancel / close イベント |
Esc・閉じた後 | Esc では cancel → close の順に発火。close で returnValue を読む |
autofocus |
最初のフォーカス先 | 付けた要素にフォーカスが移る。無い場合は最初のフォーカス可能要素 |
aria-labelledby / aria-describedby |
読み上げ用の名前と説明 | 見出しと説明文の id を指す。role="dialog" は暗黙なので不要 |
inert(背面) |
背面の操作無効化 | showModal() が自動で行う。自分で inert 属性を付ける必要はない |
対応ブラウザ(MDN のブラウザ互換データ @mdn/browser-compat-data 8.1.3、2026-09-24 版で確認)
| 機能 | Chrome / Edge | Firefox | Safari |
|---|---|---|---|
<dialog>・show()・showModal()・close()・returnValue・::backdrop |
37 / 79 | 98 | 15.4 |
:modal |
105 | 103 | 15.6 |
inert 属性 |
102 | 112 | 15.5 |
popover 属性 |
114 | 125 | 17 |
@starting-style |
117 | 129 | 17.5 |
transition-behavior: allow-discrete |
117 | 129 | 17.4 |
overlay(transition に書く用) |
117 | 未対応(書いても無視されるだけ) | 未対応 |
:open |
133 | 136 | 26.5 |
requestClose() |
134 | 139 | 18.4 |
closedby 属性 / closedBy プロパティ |
134 | 141 | Technology Preview のみ |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「HTML dialog要素でモーダル実装|コピペ完成版とa11y対応」
コピペ完成版:開閉・Esc・外側クリック・スクロール固定・アニメーション・a11y
次の1ファイルをそのまま保存してブラウザで開けば動きます。 外部ライブラリは不要です。「備品の貸出申請」という架空の確認モーダルを例にしています。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>dialog要素モーダル サンプル</title>
<style>
/* モーダルが開いている間だけ背面ページのスクロールを止める */
body:has(dialog[open]) { overflow: hidden; }
.modal {
width: min(90vw, 480px);
padding: 0; /* 余白は内側の form に持たせる(外側クリック判定のため) */
border: 0;
border-radius: 12px;
box-shadow: 0 20px 60px rgb(0 0 0 / .3);
overscroll-behavior: contain; /* モーダル内のスクロールが背面に連鎖しない */
/* 閉じるときの終点 */
opacity: 0;
translate: 0 16px;
transition:
opacity .25s ease,
translate .25s ease,
overlay .25s ease allow-discrete, /* 最上位レイヤーから外れるのを遅らせる */
display .25s ease allow-discrete; /* display:none になるのを遅らせる */
}
.modal[open] {
opacity: 1;
translate: 0 0;
/* 開く直前の状態(ここから遷移が始まる) */
@starting-style {
opacity: 0;
translate: 0 16px;
}
}
.modal::backdrop {
background: rgb(0 0 0 / .5);
opacity: 0;
transition:
opacity .25s ease,
overlay .25s ease allow-discrete,
display .25s ease allow-discrete;
}
.modal[open]::backdrop {
opacity: 1;
@starting-style { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.modal, .modal::backdrop { transition: none; }
}
.modal__body { padding: 24px; margin: 0; }
.modal__actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; }
</style>
</head>
<body>
<h1>備品の貸出</h1>
<button type="button" id="openBtn">貸出を申請する</button>
<p id="result" aria-live="polite"></p>
<p style="height: 200vh">(長いページの例)</p>
<dialog id="lendDialog" class="modal" closedby="any"
aria-labelledby="lendTitle" aria-describedby="lendDesc">
<form method="dialog" class="modal__body">
<h2 id="lendTitle">貸出を申請しますか?</h2>
<p id="lendDesc">申請すると担当者に通知が届きます。</p>
<label>メモ <input name="memo" autofocus></label>
<div class="modal__actions">
<button type="submit" value="cancel">やめる</button>
<button type="submit" value="ok">申請する</button>
</div>
</form>
</dialog>
<script>
const dialog = document.getElementById('lendDialog');
const openBtn = document.getElementById('openBtn');
const result = document.getElementById('result');
openBtn.addEventListener('click', () => {
dialog.returnValue = ''; // Esc で閉じても前回の値が残らないように毎回リセット
dialog.showModal(); // 背面を inert にして最上位レイヤーに表示
});
// closedby="any" 非対応ブラウザ向け:外側(::backdrop)クリックで閉じる
if (!('closedBy' in dialog)) {
dialog.addEventListener('click', (e) => {
if (e.target === dialog) dialog.close('cancel');
});
}
// 閉じたら returnValue(押したボタンの value)を受け取る
dialog.addEventListener('close', () => {
result.textContent = `returnValue: "${dialog.returnValue}"`;
});
</script>
</body>
</html>
動かすと次のようになります(Chromium 141 で確認)。
| 操作 | 結果 |
|---|---|
| 「貸出を申請する」を押す | モーダルが下から浮き上がって表示され、背面が暗くなる。フォーカスは「メモ」入力欄に移る |
| 「申請する」/「やめる」を押す | 閉じて returnValue: "ok"/returnValue: "cancel" と表示。フォーカスは「貸出を申請する」ボタンに戻る |
| Esc を押す | 閉じて returnValue: "" と表示 |
| 背面(暗い部分)をクリック | closedby="any" により閉じる。非対応ブラウザでは JS のフォールバックが閉じる |
| モーダルの上でホイールを回す | 背面ページはスクロールしない(scrollY が動かない) |
要点は3つです。 showModal() がモーダル表示・背面 inert・Esc・フォーカス移動をまとめて担う、padding: 0 にして余白を内側の form に持たせることで外側クリック判定(e.target === dialog)が正確になる、aria-labelledby と aria-describedby で支援技術に「何のダイアログか」を伝える(role="dialog" は暗黙に付くので不要)、の3点です。それぞれの挙動は次の検証結果で確かめます。
Playwright で検証した結果(Chromium 141)
上の完成版と比較用の小さなページを Playwright 1.56.0(Chromium 141.0.7390.37、Node 22.22)で操作し、document.activeElement や getComputedStyle の値を取り出しました。 ローカルの HTTP サーバー(ポート 7730)で完成版を配信し、page.click('#openBtn') → page.keyboard.press('Escape') のように実際の操作を行ったうえで、page.evaluate() で値を読み取っています。
開いたときのフォーカスと背面の inert
| 確認項目 | 結果 |
|---|---|
showModal() 直後の activeElement(autofocus あり) |
input[name=memo](dialog 内) |
showModal() 直後の activeElement(autofocus なしの dialog) |
button#p1(dialog 内の最初のフォーカス可能要素) |
dialog.matches(':modal') / matches(':open') |
true / true |
getComputedStyle(body).overflow(開く前 → 開いた後) |
visible → hidden |
getComputedStyle(dialog, '::backdrop').backgroundColor |
rgba(0, 0, 0, 0.5) |
document.elementFromPoint(20, 20)(背面の見出しがある位置) |
dialog#lendDialog(背面には届かない) |
背面の openBtn.focus() を呼んだ後の activeElement |
input[name=memo] のまま(背面にはフォーカスできない) |
page.click('#openBtn') |
1500ms でタイムアウト。エラー文:<dialog open="" ... id="lendDialog" ...> intercepts pointer events |
Tab を5回押したときの activeElement の遷移 |
button[value=cancel] → button[value=ok] → body → input[name=memo] → button[value=cancel](背面のボタンには一度も移らない) |
ポイントは、フォーカストラップを自作していないのに、Tab が背面の要素へ移らないことです。 末尾の要素で Tab を押すと一瞬 body(文書の先頭に戻る境界)を経由しますが、次に移る先は再び dialog 内の input でした。
Esc で閉じたときの流れとフォーカス復帰
| 確認項目 | 結果 |
|---|---|
| 発火したイベントの順番 | cancel → close |
直後の dialog.open / display / opacity / body overflow |
false / block / 1 / visible |
100ms 後の display / opacity(閉じるアニメーション中) |
block / 0.423933 |
500ms 後の display / opacity |
none / 0 |
ボタンをクリックして開き、Esc で閉じた後の activeElement |
button#openBtn(開く前の要素に自動で復帰) |
「申請する」で閉じた後 / 外側クリックで閉じた後の activeElement |
いずれも button#openBtn |
スクリプトから直接 showModal() して Esc で閉じた後の activeElement |
body(showModal() 時点でフォーカスがあった要素に戻る) |
フォーカス復帰先は「showModal() を呼んだ時点でフォーカスがあった要素」です。 ボタンのクリックから開けば自動でそのボタンへ戻るため、自分で openBtn.focus() を書く必要はありません。タイマーや通信完了など、ボタン以外の経路で開くときは復帰先が body になることがあるので、close イベントで明示的に focus() を呼ぶと親切です。
returnValue に何が入るか
| 閉じ方 | returnValue(Chromium 141) |
|---|---|
<form method="dialog"> の「申請する」(value="ok")/「やめる」(value="cancel") |
"ok"/"cancel" |
dialog.close('timeout') / dialog.requestClose('later') |
"timeout"/"later" |
dialog.close() 引数なし(直前に 'keep' をセット) |
"keep"(変わらない) |
close('ok') で閉じた後に再度 showModal() した直後 |
"ok"(開いてもリセットされない) |
新しいページで returnValue = 'y' をセット → Esc キー |
"" |
新しいページで returnValue = 'y' をセット → 外側クリック(closedby="any") |
"y"(変わらない) |
requestClose() 引数なしを一度呼んだ後に 'z' をセット → Esc キー |
"z"(変わらない) |
Esc と外側クリックで returnValue の扱いが揃わず、呼び出し履歴でも結果が変わりました。 仕様では「値が渡されなければ returnValue はそのまま」ですが、実装差やバージョン差が出やすい部分です。そのため完成版では 開く前に returnValue = '' でリセットし、close イベントでは「空文字=キャンセル扱い」 にしています。これなら「ok なら確定、それ以外はキャンセル」とどのブラウザでも判定できます。
show() と showModal() の違い
| 確認項目 | showModal() |
show() |
|---|---|---|
開いた直後の activeElement |
button#p1(dialog 内) |
button#p1(dialog 内。フォーカス移動は共通) |
matches(':modal') |
true |
false |
背面の outsideInput.focus() の後の activeElement |
p1(背面にフォーカスできない) |
outsideInput(背面にフォーカスできる) |
背面のボタンを page.click |
遮られてタイムアウト | クリックできた |
Esc を押した後の dialog.open |
false(閉じる) |
true(閉じない) |
dialog.closedBy の既定値 |
closerequest |
none |
::backdrop |
描画される | 描画されない |
確認ダイアログや入力フォームのように「返事をもらうまで他を触らせない」なら showModal()、通知やヒントのように「出したまま作業を続けてよい」なら show()(または後述の popover) が使い分けの基準です。
closedby 属性と requestClose()
| 確認項目 | 結果 |
|---|---|
closedby="any" で背面(30, 30)をクリック → dialog.open |
false(閉じた) |
closedby を外す(モーダルの既定 closerequest)→ 背面クリック → dialog.open |
true(閉じない) |
closedby="none" → Esc → dialog.open |
true(Esc でも閉じない) |
closedby="none" → requestClose() → dialog.open |
false(JS からは閉じられる) |
cancel イベントで preventDefault() → requestClose() → dialog.open |
true(閉じるのを止められた) |
closedby="none" は「入力途中の内容を Esc で失わせたくない」フォームに向き、requestClose() は「保存していない変更があります」と確認してから閉じる流れを cancel イベントだけで実現できます。 未対応ブラウザでは属性が無視されるだけなので、完成版のように 'closedBy' in dialog(検証では true)で判定してクリックのフォールバックを足せば両対応になります。
開閉アニメーションの仕組み(@starting-style と allow-discrete)
display: none の要素には transition が効かない、という従来のルールを @starting-style(表示された瞬間の値を宣言する)と transition-behavior: allow-discrete(display: none への切り替えを遷移の終わりまで遅らせる)が解決します。 overlay を transition に書いておくと、Chrome / Edge では閉じるときに最上位レイヤーから外れるのも遅らせられます(他のブラウザでは無視されるだけです)。検証で取得した値は次のとおりです。
| 確認項目 | 結果 |
|---|---|
getComputedStyle(dialog).transitionBehavior |
normal, normal, allow-discrete, allow-discrete |
showModal() 直後に走っている transition(getAnimations({ subtree: true })) |
3件:opacity、translate、opacity(::backdrop) |
80ms 後の opacity / translate / ::backdrop の opacity |
0.445161 / 0px 8.87742px / 0.445161(遷移の途中) |
| 400ms 後の同じ値 | 1 / 0px / 1(遷移完了) |
Esc 直後の display / 100ms 後 / 500ms 後 |
block / block(opacity 0.42) / none |
prefers-reduced-motion: reduce をエミュレート → getAnimations() の件数 |
0件(アニメーションなしで即表示) |
display を transition に含めないと、閉じた瞬間に display: none になってフェードアウトが見えません。 逆に、開くときは @starting-style が無いと、開いた瞬間に終点の値が適用されてフェードインが起きません。両方セットで書くのがコツです。
背面のスクロール固定の考え方
| 方法 | 書き方 | 効果と注意 |
|---|---|---|
開いている間だけ body を固定 |
body:has(dialog[open]) { overflow: hidden } |
JS 不要。検証では scrollY 300 の位置で開き、モーダル上でホイールを 500px 回しても scrollY は 300 のまま。閉じた後は再び動いた(834) |
| モーダル内のスクロール連鎖を止める | dialog { overscroll-behavior: contain } |
内容が長いモーダルで端まで行っても背面に伝わらない |
| スクロールバー消失によるガタつきを抑える | html { scrollbar-gutter: stable } |
スクロールバー分の幅を常に確保し、固定時のレイアウト移動を防ぐ |
iOS Safari については、この記事の検証環境(Linux の Chromium)では確認できていません(未実行)。 body { overflow: hidden } だけでは背面が動くという報告が多い環境なので、タッチ端末を重視する場合は、開く際に body を position: fixed; top: -{scrollY}px; width: 100% にし、閉じる際に scrollTo() で位置を戻す方式を併用すると安定します。
popover 属性との使い分け
popover は「軽い吹き出し・メニュー・トースト」のための非モーダルな仕組み、dialog は「返事をもらう」ためのモーダルにも使える仕組み と考えると選びやすくなります。
| 観点 | <dialog> + showModal() |
<dialog> + show() |
popover 属性 |
|---|---|---|---|
| モーダル(背面を操作不可に) | する | しない | しない |
| Esc で閉じる | 閉じる(closedby="none" で止められる) |
閉じない | popover="auto" は閉じる、manual は閉じない |
| 外側クリックで閉じる | closedby="any" のとき |
しない | auto は閉じる(ライトディスミス) |
| 開いたときのフォーカス移動 | 中へ移る | 中へ移る | 移らない(検証では activeElement は押したボタンのまま) |
::backdrop |
描画(既定 rgba(0,0,0,0.1)) |
なし | 描画されるが既定は透明(rgba(0, 0, 0, 0)) |
| JS なしで開ける | 不可(open 属性直書きは非モーダル) |
不可 | 可(popovertarget 属性) |
returnValue / <form method="dialog"> |
使える | 使える | なし |
| 向いている用途 | 確認・入力・削除前の最終確認 | 通知・ヒント | メニュー・ツールチップ・トースト・候補一覧 |
検証では popover="auto" を開いた状態で showModal() を呼ぶと popover は自動で閉じ、モーダルが前面に出ました(elementFromPoint の結果は dialog#plain)。
React / Vue から使う最小例
どちらも「ref で dialog 要素を取り、showModal() / close() を呼ぶ」だけです。 状態を open 属性にバインドすると非モーダルになるので、必ずメソッドを呼びます(この2例はコードの形を示すもので、本記事では実行していません)。
// React:open の変化で showModal()/close() を呼ぶ
import { useEffect, useRef } from 'react';
export function ConfirmDialog({ open, onClose, children }) {
const ref = useRef(null);
useEffect(() => {
const dialog = ref.current;
if (open && !dialog.open) dialog.showModal();
if (!open && dialog.open) dialog.close();
}, [open]);
return (
<dialog ref={ref} onClose={() => onClose(ref.current.returnValue)} closedby="any">
<form method="dialog">
{children}
<button value="cancel">やめる</button>
<button value="ok">申請する</button>
</form>
</dialog>
);
}
<!-- Vue 3:defineExpose でメソッドを親に公開する形 -->
<script setup>
import { ref } from 'vue';
const dialog = ref(null);
const emit = defineEmits(['close']);
defineExpose({
open: () => dialog.value.showModal(),
close: (value) => dialog.value.close(value),
});
</script>
<template>
<dialog ref="dialog" closedby="any" @close="emit('close', dialog.returnValue)">
<form method="dialog">
<slot />
<button value="cancel">やめる</button>
<button value="ok">申請する</button>
</form>
</dialog>
</template>
closedby のような新しい属性は、React でも Vue でも小文字のまま書けば DOM 属性として出力されます(React 16 以降は未知の属性もそのまま渡されます)。
よくあるつまずきと実際のエラー文
次の表は、検証で実際に出た例外メッセージと、起きた挙動をそのまま載せています。
| つまずき | 起きること(Chromium 141 の実測) | 直し方 |
|---|---|---|
HTML に open 属性を直接書く |
非モーダルで開く。matches(':modal') は false、Esc で閉じず、フォーカスも dialog の中へ移らない(検証では背面のボタンに残ったまま) |
open 属性は書かず、JS で showModal() を呼ぶ |
open 属性がある dialog、または show() で開いた dialog に showModal() |
InvalidStateError: Failed to execute 'showModal' on 'HTMLDialogElement': The dialog is already open as a non-modal dialog, and therefore cannot be opened as a modal dialog. |
先に close() してから showModal()、または開き方を片方に統一する |
showModal() で開いた dialog に show() |
InvalidStateError: Failed to execute 'show' on 'HTMLDialogElement': The dialog is already open as a modal dialog, and therefore cannot be opened as a non-modal dialog. |
同上 |
showModal() を2回呼ぶ |
例外は出ず、2回目は無視された(show() 2回も同じ)。以前の仕様では例外だったので、記事によって説明が異なる |
if (!dialog.open) dialog.showModal() と書けばどの版でも安全 |
DOM に追加する前に showModal() |
InvalidStateError: Failed to execute 'showModal' on 'HTMLDialogElement': The element is not in a Document. |
document.body.append(dialog) の後に呼ぶ |
dialog 自体に padding を付けて外側クリック判定 |
余白をクリックしても e.target === dialog になり閉じる |
padding: 0 にして内側の要素に余白を持たせる |
Esc の後に returnValue を見る |
前回の値が消えるか残るかが閉じ方で変わる | 開く前に returnValue = ''、close で「空文字=キャンセル」 |
closedby="any" が効かない |
Safari は Technology Preview のみ対応(2026-09-24 時点) | 'closedBy' in dialog で判定してクリックのフォールバックを足す |
| 閉じてもフォーカスが戻らない | 復帰先は showModal() 時点の activeElement。スクリプトから開くと body に戻る |
close イベントで戻したい要素に focus() する |
showModal() の二重呼び出しは、現在の仕様では「すでにモーダルで開いていれば何もしない」に変わっています。 一方で、open 属性直書きや show() で開いた非モーダル状態からの showModal() は例外になります。「開き方を混ぜない」「dialog.open を見てから呼ぶ」の2点を守れば、どのバージョンでも例外になりません。
よくある質問
Q. role="dialog" や aria-modal="true" は書く必要がありますか?
A. 不要です。<dialog> は暗黙に role="dialog" を持ち、showModal() で開くとモーダルとして扱われます。書くべきなのは aria-labelledby(見出しの id)と、説明文があるなら aria-describedby です。
Q. [open] と :open と :modal はどう使い分けますか?
A. [open] は属性セレクタで、どのブラウザでも使えます。:open はそれと同じ意味の疑似クラスで、Chrome 133 / Firefox 136 / Safari 26.5 以降です。:modal は「モーダルで開いているときだけ」当たるので、showModal() のときだけ適用したいスタイルに向きます。
Q. モーダルの中でさらにモーダルを開けますか?
A. 開けます。検証では、1枚目を開いたまま2枚目を showModal() すると、フォーカスは2枚目の中へ移り、1枚目の中心で elementFromPoint を取ると2枚目が返り(上に積まれている)、Esc は1回目で2枚目だけ、2回目で1枚目が閉じました。利用者が迷いやすいので、1枚目の中で完結させる設計も先に検討します。
まとめ
showModal()を1回呼ぶだけで、最上位レイヤー表示・背面 inert・Esc・フォーカス移動と復帰がそろう(検証:Tab は背面に移らず、閉じると開いたボタンに戻った)show()は非モーダル(背面にフォーカスでき、Esc で閉じず、::backdropも出ない)。open属性の直書きも非モーダル<form method="dialog">のボタンのvalueがreturnValueに入る。Esc の扱いはブラウザで揺れるので、開く前にリセットして「空文字=キャンセル」にする- 外側クリックは
closedby="any"(Chrome 134 / Firefox 141)。'closedBy' in dialogで判定してクリックのフォールバックを足すと両対応 - 開閉アニメーションは
@starting-styleとtransition: display ... allow-discreteのセット。::backdropも同じ書き方でフェードできる - スクロール固定は
body:has(dialog[open]) { overflow: hidden }とoverscroll-behavior: contain。iOS はposition: fixed方式の併用を検討 - 例外が出るのは「非モーダルで開いている dialog を
showModal()」と「DOM 未追加」。showModal()の二重呼び出しは現在の仕様では無視される
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「HTML dialog要素でモーダル実装|コピペ完成版とa11y対応」
株式会社RJCでは、dialog 要素のような標準技術をフロントエンドの実装に取り入れ、動かして確かめた結果を仲間と共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。